From fd1e6ecf4bd458ac3f5d70aef9663a791ca0724b Mon Sep 17 00:00:00 2001 From: Bartosz Sofinski Date: Tue, 7 Jul 2026 19:11:43 +0200 Subject: [PATCH 1/2] Avoid overhead for elements without ref prop --- .../modules/createStrictDOMComponent.js | 6 +- .../modules/createStrictDOMImageComponent.js | 6 +- .../modules/createStrictDOMTextComponent.js | 6 +- .../src/native/modules/useStrictDOMElement.js | 56 ++--- .../__snapshots__/compat-test.native.js.snap | 7 - ...contexts-ThemeProvider-test.native.js.snap | 22 -- ...texts-ViewportProvider-test.native.js.snap | 3 - .../css-create-test.native.js.snap | 40 ---- .../css-themes-test.native.js.snap | 7 - .../__snapshots__/html-test.js.snap-native | 194 ------------------ .../__snapshots__/html-test.native.js.snap | 23 --- 11 files changed, 44 insertions(+), 326 deletions(-) diff --git a/packages/react-strict-dom/src/native/modules/createStrictDOMComponent.js b/packages/react-strict-dom/src/native/modules/createStrictDOMComponent.js index 3290355c..2cac69e3 100644 --- a/packages/react-strict-dom/src/native/modules/createStrictDOMComponent.js +++ b/packages/react-strict-dom/src/native/modules/createStrictDOMComponent.js @@ -41,7 +41,7 @@ function applyViewProps( tagName: string, isPressable: boolean, disabled: boolean, - elementRef: CallbackRef, + elementRef: ?CallbackRef, displayInsideValue: 'flow' | 'flex' ): 'flow' | 'flex' { // Tag-specific props @@ -63,7 +63,9 @@ function applyViewProps( nativeProps.focusable = false; } - nativeProps.ref = elementRef; + if (elementRef != null) { + nativeProps.ref = elementRef; + } // Workaround: React Native doesn't support raw text children of View // Sometimes we can auto-fix this diff --git a/packages/react-strict-dom/src/native/modules/createStrictDOMImageComponent.js b/packages/react-strict-dom/src/native/modules/createStrictDOMImageComponent.js index fc0fbbbf..bb0b86f4 100644 --- a/packages/react-strict-dom/src/native/modules/createStrictDOMImageComponent.js +++ b/packages/react-strict-dom/src/native/modules/createStrictDOMImageComponent.js @@ -26,7 +26,7 @@ import * as css from '../css'; function applyImageProps( nativeProps: ReactNativeProps, props: StrictReactDOMImageProps, - elementRef: CallbackRef + elementRef: ?CallbackRef ): void { const { alt, @@ -85,7 +85,9 @@ function applyImageProps( // Component-specific props - nativeProps.ref = elementRef; + if (elementRef != null) { + nativeProps.ref = elementRef; + } } export function createStrictDOMImageComponent< diff --git a/packages/react-strict-dom/src/native/modules/createStrictDOMTextComponent.js b/packages/react-strict-dom/src/native/modules/createStrictDOMTextComponent.js index 569160f8..339f4e1b 100644 --- a/packages/react-strict-dom/src/native/modules/createStrictDOMTextComponent.js +++ b/packages/react-strict-dom/src/native/modules/createStrictDOMTextComponent.js @@ -38,7 +38,7 @@ function applyTextProps( nativeProps: ReactNativeProps, props: StrictProps, tagName: string, - elementRef: CallbackRef + elementRef: ?CallbackRef ): void { const { href, label } = props; @@ -70,7 +70,9 @@ function applyTextProps( // Component-specific props - nativeProps.ref = elementRef; + if (elementRef != null) { + nativeProps.ref = elementRef; + } // Workaround: Android doesn't support ellipsis truncation if Text is selectable // See #136 diff --git a/packages/react-strict-dom/src/native/modules/useStrictDOMElement.js b/packages/react-strict-dom/src/native/modules/useStrictDOMElement.js index a8a6c7bd..10d13577 100644 --- a/packages/react-strict-dom/src/native/modules/useStrictDOMElement.js +++ b/packages/react-strict-dom/src/native/modules/useStrictDOMElement.js @@ -12,7 +12,6 @@ import type { HostInstance } from '../../types/renderer.native'; import * as React from 'react'; -import { useElementCallback } from '../../shared/useElementCallback'; import { useViewportScale } from './ContextViewportScale'; type Options = { @@ -168,32 +167,41 @@ function getOrCreateStrictRef( export function useStrictDOMElement( ref: React.RefSetter, { tagName }: Options -): CallbackRef { +): ?CallbackRef { const { scale: viewportScale } = useViewportScale(); - return useElementCallback( - React.useCallback( - (node: HostInstance) => { - if (ref == null) return undefined; - const strictRef = getOrCreateStrictRef(node, tagName, viewportScale); - if (typeof ref === 'function') { - // Public ref type is the DOM element `T` (web/native parity); at - // runtime we hand over the RN-backed strict wrapper. - // $FlowFixMe[incompatible-type] - Flow does not understand ref cleanup. - const cleanup: void | (() => void) = ref(strictRef as $FlowFixMe); - return typeof cleanup === 'function' - ? cleanup + return React.useMemo(() => { + if (ref == null) { + return null; + } + let cleanup: void | (() => void); + return (node: HostInstance | null) => { + if (cleanup != null) { + cleanup(); + cleanup = undefined; + } + if (node == null) { + return; + } + const strictRef = getOrCreateStrictRef(node, tagName, viewportScale); + if (typeof ref === 'function') { + // Public ref type is the DOM element `T` (web/native parity); at + // runtime we hand over the RN-backed strict wrapper. + // $FlowFixMe[incompatible-type] - Flow does not understand ref cleanup. + const refCleanup: void | (() => void) = ref(strictRef as $FlowFixMe); + cleanup = + typeof refCleanup === 'function' + ? refCleanup : () => { ref(null); }; - } - // $FlowFixMe[incompatible-type] - see the ref-callback note above. - ref.current = strictRef; - return () => { - ref.current = null; - }; - }, - [ref, tagName, viewportScale] - ) - ); + return; + } + // $FlowFixMe[incompatible-type] - see the ref-callback note above. + ref.current = strictRef; + cleanup = () => { + ref.current = null; + }; + }; + }, [ref, tagName, viewportScale]); } diff --git a/packages/react-strict-dom/tests/compat/__snapshots__/compat-test.native.js.snap b/packages/react-strict-dom/tests/compat/__snapshots__/compat-test.native.js.snap index 7e6b6b79..427a6713 100644 --- a/packages/react-strict-dom/tests/compat/__snapshots__/compat-test.native.js.snap +++ b/packages/react-strict-dom/tests/compat/__snapshots__/compat-test.native.js.snap @@ -3,7 +3,6 @@ exports[` "as" equals "div": as=div 1`] = ` "as" equals "div": as=div 1`] = ` exports[` "as" equals "img": as=img 1`] = ` "as" equals "span": as=span 1`] = ` "as" equals "textarea": as=textarea 1`] = ` exports[` default: default 1`] = ` default: default 1`] = ` exports[` nested: nested 1`] = ` nested: nested 1`] = ` exports[` styled: styled 1`] = ` css variable declaration inside a media query 1`] = ` css variable declaration inside a media qu exports[` defines global custom properties 1`] = ` [ defines global custom properties 1`] = ` Expect color:green , defines global custom properties 1`] = ` exports[` kebab case string var to camel case 1`] = ` [ kebab case string var to camel case 1`] = } />, kebab case string var to camel case 1`] = exports[` number var lookup works 1`] = ` number var lookup works 1`] = ` exports[` number var value lookup with reference to another var 1`] = ` [ number var value lookup with reference to } />, number var value lookup with reference to exports[` rgb(a) function with args applied through a single var 1`] = ` [ rgb(a) function with args applied through } />, rgb(a) function with args applied through exports[` rgba function with args applied through multiple (& nested) vars 1`] = ` rgba function with args applied through mu exports[` string var 1`] = ` string var 1`] = ` exports[` string var and falls back to a default value containing spaces and embedded var 1`] = ` string var and falls back to a default val exports[` string var and falls back to default value containing a var 1`] = ` string var and falls back to default value exports[` string var value lookup with em prop conversion 1`] = ` string var value lookup with em prop conve exports[` string var value lookup with pixel prop conversion 1`] = ` string var value lookup with pixel prop co exports[` string var with a default value 1`] = ` [ string var with a default value 1`] = ` } />, string var with a default value 1`] = ` exports[` string var with a default value containing spaces 1`] = ` [ string var with a default value containing } />, string var with a default value containing exports[` textShadow with nested/multiple vars 1`] = ` textShadow with nested/multiple vars 1`] = exports[` transform with nested/multiple vars 1`] = ` all CSS lengths are scaled according to viewport width: scaled lengths 1`] = ` all CSS lengths are scaled according to } > all CSS lengths are scaled according to Scaled content