Skip to content

Apply setNativeProps props to a cloned node only when they changed - #59624

Open
hryhoriiK97 wants to merge 1 commit into
react:mainfrom
hryhoriiK97:fabric-skip-reapplying-native-props
Open

hryhoriiK97 wants to merge 1 commit into
react:mainfrom
hryhoriiK97:fabric-skip-reapplying-native-props

Conversation

@hryhoriiK97

Copy link
Copy Markdown
Contributor

Summary:

Once a component's props are set with setNativeProps, ShadowNode::clone applies family.nativeProps_DEPRECATED again every time one of its shadow nodes is cloned with unchanged props: it parses them into new props with cloneProps. That includes the clones Yoga makes of every child of a node it lays out (YogaLayoutableShadowNode::adoptYogaChild), so each commit that touches a parent re-parses the native props of all its children that ever had setNativeProps, and nativeProps_DEPRECATED stays set for the life of the component.

On Fabric, JS-driven Animated (useNativeDriver: false) updates props with setNativeProps on every frame (shouldUseSetNativePropsInFabric, on by default). With several animated components under one parent, every frame re-parses the props of all of them, not just the one being updated. For a Text, new props also mark its Yoga node dirty (ParagraphShadowNode::shouldNewRevisionDirtyMeasurement), so all of them are measured again. In a list of 30 JS-animated texts, on Android that was ~16,000 measurePreparedLayout calls per second (~40 per frame), and on iOS props parsing was 27–28% of the JS thread.

With this change, ShadowNodeFamily counts changes to nativeProps_DEPRECATED (setNativeProps_DEPRECATED, and UIManager::cloneNode when a React render updates them), and each shadow node records the count its props include. A clone with unchanged props keeps the source node's count; ShadowNode::clone applies nativeProps_DEPRECATED only when the node it clones is behind, exactly as before in every other case. So native props are still applied after setNativeProps and after a React render replaces a node's props; they are only no longer parsed again into props that already include them.

Changelog:

[GENERAL] [CHANGED] - Fabric no longer parses the props set by setNativeProps again on every clone of a component's shadow nodes

Test Plan:

  • New FabricUIManagerTest cases: after setNativeProps_DEPRECATED, a clone of a node whose props include them keeps the same props object, and a later setNativeProps_DEPRECATED is applied to the next clone; after a React render (cloneNode) replaces a node's props, the next clone applies the native props again. These C++ tests run internally; locally I compiled the test file against the real headers with a stub gtest (clang++ -fsyntax-only), as OSS CI doesn't run ReactCommon gtests.
  • yarn fantom with the setNativeProps reconciliation tests (Mounting-itest.js, ReactNativeElement-itest.js) and the Animated suites that drive JS animations through setNativeProps (AnimatedComponents, AnimatedComposition, AnimatedValue, TimingAnimation): 6 suites, 285 tests pass.
  • node scripts/clang-format.js --check reports no changes for the touched files.
  • No C++ API snapshot changes: the new ShadowNodeFamily field is excluded by _DEPRECATED and the new ShadowNode field is private.
  • On devices, a Playground screen of 30 Animated.Text rows with JS-driven loops (even rows animate color, odd rows fontSize), RNTester release builds:
    • Android, moto g57 power (Android 16), two APKs from the same local checkout (main at f7de203 with local benchmark screens and call counters), installed alternately, 20 s per run, three rounds. "Before" is a build with this check behind a runtime switch, turned off, so clone re-applies nativeProps_DEPRECATED every time as on main; "this PR" is this change as is:
before this PR change
measurePreparedLayout JNI calls per second 16,038 / 16,151 / 15,986 2,079 / 2,073 / 2,068 −87%
Text draws per second (animation throughput) 548 / 551 / 548 762 / 754 / 761 +39%
JS thread, 20 s (saturated in both) 20.5 / 20.7 / 20.6 s 19.0 / 18.9 / 18.9 s −8%, −33% per Text draw
Main thread, 20 s 3.52 / 3.44 / 3.50 s 3.24 / 3.10 / 3.20 s −9%

A control screen without setNativeProps (200 single-style numbers changing every 100 ms) is unchanged: JS time per text layout 0.45 → 0.44 ms, main thread 2.30 → 2.33 s.

  • iOS, iPhone 15 Pro (iOS 26.6.1), the same check behind a runtime switch in one build, Time Profiler for 12 s per run, two rounds each: props parsing (cloneProps and its callees) fell from 27–28% of the JS thread (1,608 / 2,708 ms) to 4.4–4.5% (158 / 208 ms). Total JS and main-thread times varied too much between runs on this phone to compare.

🤖 Generated with Claude Code

Once a component's props were set with setNativeProps, every clone of its
shadow nodes with unchanged props parsed nativeProps_DEPRECATED into new
props again, including the clones Yoga makes of every child of a node it
lays out. New props mark a Text's Yoga node dirty, so with JS-driven
Animated (which uses setNativeProps on Fabric) every sibling Text was
measured again in every frame.

The family now counts changes to nativeProps_DEPRECATED, and each node
records the count its props include. A clone applies nativeProps_DEPRECATED
only when the node it clones is behind.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Oct 29, 2026
@facebook-github-tools facebook-github-tools Bot added p: Software Mansion Partner: Software Mansion Partner Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. labels Oct 29, 2026

This branch has not been deployed

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

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. p: Software Mansion Partner: Software Mansion Partner Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant