Skip to content

Adopt React Native 0.82 DOM Node APIs#475

Open
yaminyassin wants to merge 3 commits into
facebook:mainfrom
yaminyassin:rn-0.82-dom-node-apis
Open

Adopt React Native 0.82 DOM Node APIs#475
yaminyassin wants to merge 3 commits into
facebook:mainfrom
yaminyassin:rn-0.82-dom-node-apis

Conversation

@yaminyassin
Copy link
Copy Markdown

@yaminyassin yaminyassin commented May 12, 2026

Summary

Bumps react-native peer dependency to >=0.82.0 and rewrites useStrictDOMElement to wrap the underlying RN host node in a thin Proxy instead of cloning it via Object.create / Object.defineProperties. React Native 0.82 ships the stable DOM Node APIs (DOM traversal, ownerDocument, getRootNode, children / childNodes, pointer-capture, etc.) that strict-dom helped drive into RN — adopting them as the substrate turns the native ref polyfill into a lightweight overlay.

Tracks discussion #462.

What's still polyfilled

The Proxy traps only the keys strict-dom still needs to control:

  • nodeName → uppercase DOM name (RN exposes tagName as 'RN:View')
  • getBoundingClientRect + length getters → divided by the active viewportScale
  • <img>.complete → fallback to false when the underlying RN Image node doesn't expose it
  • <input> / <textarea> setSelectionRange / selectionStart / selectionEnd → polyfilled on top of setSelection. Self-disables the day RN exposes the W3C selection API.

What now passes through to the RN node

ownerDocument, getRootNode, parentNode, parentElement, children, childNodes, sibling navigation, contains, compareDocumentPosition, setPointerCapture / hasPointerCapture / releasePointerCapture, and legacy measure / measureInWindow / measureLayout / setNativeProps. The Proxy forwards function values bound to the underlying target so internal this-references inside RN's implementations resolve correctly. [[GetPrototypeOf]] and Symbol reads forward by default — instanceof keeps working.

Identity caching via a WeakMap<Node, Proxy> is preserved so the same RN node always yields the same wrapped ref.

Breaking

Peer dep react-native >=0.79.5>=0.82.0. Apps on older RN must stay on the previous react-strict-dom release.

Bundle size impact

dist/native/index.js drops −363 bytes minified / −69 bytes brotli. Web build is byte-identical. Other bundles unchanged.

Perf benchmarks (css.create, css.createTheme) move within ±2% — noise. Those benchmarks don't exercise the ref path, so they confirm the unrelated style system didn't regress.

Notes for reviewers

  • Expo SDK pairing. No Expo SDK targets exactly RN 0.82 (SDK 53 = RN 0.79, SDK 54 ≈ RN 0.81, SDK 55 = RN 0.83). The example apps land on SDK 55 / RN 0.83.6, which satisfies the new peer dep and ships the same DOM Node APIs.
  • React devDeps. RN 0.82+ requires React >=19.1.1. The workspace root and library packages' devDependencies pin react / react-dom / react-test-renderer to ~19.2.0 to keep a single React instance across the monorepo.
  • Flow type changes in RN 0.83. Animated.createAnimatedComponent is now a single-type-arg generic — fixed properly by dropping the over-specified two-type-arg form. ImageProps / TextInputProps became exact; the wide-spread of strict-dom's ReactNativeProps onto those hosts is covered with two targeted $FlowFixMe annotations. Properly narrowing the spread is a real follow-up.
  • 10 new ref tests in tests/html/html-refs-test.native.js pin every Proxy behavior: nodeName uppercase, getBoundingClientRect pass-through and scaled, DOM Node API pass-through (ownerDocument / getRootNode / childNodes / children), strict-ref identity stability, and <img>.complete fallback / pass-through.

Yamin Yassin added 3 commits May 12, 2026 23:20
Bump the react-native peer dependency to >=0.82.0 and rewrite
useStrictDOMElement to wrap the underlying RN host node in a thin
Proxy instead of cloning it via Object.create / Object.defineProperties.

React Native 0.82 shipped the stable DOM Node APIs that strict-dom
helped drive into RN (DOM traversal, ownerDocument, getRootNode,
children/childNodes, pointer-capture methods, etc.), so the native
ref polyfill becomes a lightweight overlay rather than a parallel
implementation. The Proxy traps only the keys strict-dom still needs
to control:

- nodeName: uppercase DOM name (RN exposes tagName as 'RN:View')
- getBoundingClientRect and length getters: divided by the active
  viewportScale
- <img>.complete: fallback to false when the underlying RN Image
  node does not expose it
- <input>/<textarea> selection trio (setSelectionRange,
  selectionStart, selectionEnd): polyfilled on top of setSelection
  while RN's TextInput lacks the W3C selection API

Everything else (ownerDocument, getRootNode, parentNode, children,
childNodes, sibling navigation, pointer-capture, legacy measure*)
forwards directly to the underlying RN node via Reflect.get. Function
values are bound to the target so internal `this`-references inside
RN's implementations resolve correctly.

Identity caching via a WeakMap<Node, Proxy> is preserved so the same
underlying RN node always yields the same wrapped ref.

The selection polyfill is gated on the underlying property being
absent, so the day RN exposes the W3C selection API on TextInput the
polyfill self-disables.

RN 0.83 also tightened the Flow types around Animated.createAnimatedComponent
(now a single-type-arg generic) and made ImageProps / TextInputProps
exact. The Animated factory call sites are updated to the new
signature; the wide-spread of strict-dom's ReactNativeProps onto the
exact host components is suppressed with targeted $FlowFixMe
annotations (real follow-up tracked separately).

Adds 10 ref tests in tests/html/html-refs-test.native.js documenting
the contract: uppercase nodeName, getBoundingClientRect pass-through
at scale=1, getBoundingClientRect scaled when viewportScale != 1, the
DOM Node API pass-through (ownerDocument / getRootNode / childNodes /
children), identity stability of the strict ref across renders, and
the <img>.complete fallback (both when omitted and when provided).

Bundle size: native/index.js drops ~363 minified / ~69 brotli bytes.
Web build is byte-identical.
Bring apps/expo-app and apps/platform-tests onto a real RN >=0.82.0
runtime so they exercise the new useStrictDOMElement Proxy path
against the DOM Node APIs from the previous commit.

Note: there is no Expo SDK that pairs exactly with RN 0.82 (SDK 53 =
RN 0.79, SDK 54 ~= RN 0.81, SDK 55 = RN 0.83). SDK 55 / RN 0.83.6
still satisfies the library's >=0.82.0 peer dep and ships the same
DOM Node API surface, so it is the closest landing zone.

Co-traveling dependency versions (@expo/metro-runtime,
expo-build-properties, expo-status-bar, react-native-web, etc.) come
from `npx expo install --check` for SDK 55; no hand-rolled versions.

Also pin react / react-dom / react-test-renderer to ~19.2.0 across
the workspace root and the two library packages' devDependencies.
RN 0.82+ requires React >=19.1.1, and aligning the workspace devDeps
avoids a multiple-React-instances error in the jest suite that would
otherwise surface once the apps hoist React 19.2.x at the root.
@yaminyassin
Copy link
Copy Markdown
Author

yaminyassin commented May 13, 2026

Related follow-up: #477: switches strict-dom from the deep import react-native/Libraries/Text/TextAncestor to the public unstable_TextAncestorContext API exposed in facebook/react-native#52368. Logically depends on this PR (the bumped peer dep is what makes the public API guaranteed available). Once both land, the corresponding RN-side cleanup can delete the compatibility shim.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant