From 0f0630b829ac7f203fb033371310c1bb4fb94e79 Mon Sep 17 00:00:00 2001 From: Gregory Moskaliuk Date: Thu, 29 Oct 2026 15:44:21 +0100 Subject: [PATCH 1/5] Apply setNativeProps props to a cloned node only when they changed 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 --- .../react/renderer/core/ShadowNode.cpp | 17 +++++++- .../react/renderer/core/ShadowNode.h | 6 +++ .../react/renderer/core/ShadowNodeFamily.h | 6 +++ .../react/renderer/uimanager/UIManager.cpp | 2 + .../uimanager/tests/FabricUIManagerTest.cpp | 41 +++++++++++++++++++ 5 files changed, 70 insertions(+), 2 deletions(-) diff --git a/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.cpp b/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.cpp index 4ff280cf23ee..20224379e120 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.cpp +++ b/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.cpp @@ -126,6 +126,11 @@ ShadowNode::ShadowNode( react_native_assert(children_); traits_.set(ShadowNodeTraits::Trait::ChildrenAreShared); + // A clone that keeps the source's props keeps the native props they include. + if (fragment.props == ShadowNodeFragment::propsPlaceholder()) { + nativePropsVersion_ = sourceShadowNode.nativePropsVersion_; + } + if (fragment.children) { for (const auto& child : *children_) { child->family_->setParent(family_); @@ -140,11 +145,17 @@ std::shared_ptr ShadowNode::clone( const auto& componentDescriptor = family.componentDescriptor_; std::optional propsOverride; + uint32_t nativePropsVersion = 0; { std::lock_guard lock(family.nativePropsMutex); if (family.nativeProps_DEPRECATED != nullptr) { if (fragment.props == ShadowNodeFragment::propsPlaceholder()) { - propsOverride.emplace(*family.nativeProps_DEPRECATED); + // Props of a node that already include the current + // `nativeProps_DEPRECATED` don't need them applied again. + if (nativePropsVersion_ != family.nativePropsVersion_DEPRECATED) { + propsOverride.emplace(*family.nativeProps_DEPRECATED); + nativePropsVersion = family.nativePropsVersion_DEPRECATED; + } } else { // TODO: We might need to merge fragment.props with // `family.nativeProps_DEPRECATED`. @@ -158,12 +169,14 @@ std::shared_ptr ShadowNode::clone( // result escapes the lock so the rest of the clone work runs // unblocked. auto propsParserContext = PropsParserContext{family_->getSurfaceId(), {}}; - return componentDescriptor.cloneShadowNode( + auto clonedShadowNode = componentDescriptor.cloneShadowNode( *this, {.props = componentDescriptor.cloneProps( propsParserContext, props_, std::move(*propsOverride)), .children = fragment.children, .state = fragment.state}); + clonedShadowNode->nativePropsVersion_ = nativePropsVersion; + return clonedShadowNode; } return componentDescriptor.cloneShadowNode(*this, fragment); diff --git a/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.h b/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.h index b21cb21b4f3b..7418a1e564bf 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.h +++ b/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.h @@ -282,6 +282,12 @@ class ShadowNode : public Sealable, public DebugStringConvertible, public jsi::N */ mutable std::atomic hasBeenMounted_{false}; + /* + * The family's `nativePropsVersion_DEPRECATED` that `props_` already + * include, so that cloning doesn't apply `nativeProps_DEPRECATED` again. + */ + uint32_t nativePropsVersion_{0}; + static Props::Shared propsForClonedShadowNode(const ShadowNode &sourceShadowNode, const Props::Shared &props); protected: diff --git a/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamily.h b/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamily.h index 2ffc2ce5ac2a..8a9ef564d596 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamily.h +++ b/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamily.h @@ -130,6 +130,12 @@ class ShadowNodeFamily final : public jsi::NativeState { mutable std::unique_ptr nativeProps_DEPRECATED; mutable std::mutex nativePropsMutex; + /* + * Incremented whenever `nativeProps_DEPRECATED` changes, so a cloned node + * can tell whether its props already include them. + */ + mutable uint32_t nativePropsVersion_DEPRECATED{0}; + /** * @return tag for the ShadowNodeFamily. */ diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp index 7eefe4606177..b12c41795780 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp @@ -150,6 +150,7 @@ std::shared_ptr UIManager::cloneNode( *family.nativeProps_DEPRECATED, // source (folly::dynamic)rawProps, // patch NullValueStrategy::Ignore)); + family.nativePropsVersion_DEPRECATED++; // 2. Compute the final set of props. // @@ -465,6 +466,7 @@ void UIManager::setNativeProps_DEPRECATED( auto& family = shadowNode->getFamily(); { std::lock_guard lock(family.nativePropsMutex); + family.nativePropsVersion_DEPRECATED++; if (family.nativeProps_DEPRECATED) { // Values in `rawProps` patch (take precedence over) // `nativeProps_DEPRECATED`. For example, if both diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/tests/FabricUIManagerTest.cpp b/packages/react-native/ReactCommon/react/renderer/uimanager/tests/FabricUIManagerTest.cpp index 47aaa20ef567..f8c96566adda 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/tests/FabricUIManagerTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/tests/FabricUIManagerTest.cpp @@ -155,4 +155,45 @@ TEST_F(FabricUIManagerTest, SetNativePropsMergesWithReactRender) { EXPECT_FLOAT_EQ(merged.opacity, 0.5); } +// Cloning a node whose props already include the props set by +// `setNativeProps_DEPRECATED` (as Yoga does for every child of a node it lays +// out) keeps those props instead of parsing them again. Props set later are +// still applied. +TEST_F(FabricUIManagerTest, CloneAppliesSetNativePropsOnlyOnce) { + uiManager_->setNativeProps_DEPRECATED( + currentViewNode(), RawProps(folly::dynamic::object("opacity", 0.5))); + + auto first = currentViewNode()->clone({}); + EXPECT_FLOAT_EQ( + static_cast(*first->getProps()).opacity, 0.5); + + auto second = first->clone({}); + EXPECT_EQ(second->getProps(), first->getProps()); + + uiManager_->setNativeProps_DEPRECATED( + currentViewNode(), RawProps(folly::dynamic::object("opacity", 0.25))); + auto third = second->clone({}); + EXPECT_NE(third->getProps(), second->getProps()); + EXPECT_FLOAT_EQ( + static_cast(*third->getProps()).opacity, 0.25); +} + +// After a React re-render replaces a node's props, the next clone applies the +// props set by `setNativeProps_DEPRECATED` again. +TEST_F(FabricUIManagerTest, CloneAppliesSetNativePropsAfterReactRender) { + uiManager_->setNativeProps_DEPRECATED( + currentViewNode(), RawProps(folly::dynamic::object("opacity", 0.5))); + auto applied = currentViewNode()->clone({}); + + auto rerendered = uiManager_->cloneNode( + *applied, + /*children=*/nullptr, + RawProps(folly::dynamic::object("nativeID", "from-react"))); + auto cloned = rerendered->clone({}); + EXPECT_NE(cloned->getProps(), rerendered->getProps()); + auto& props = static_cast(*cloned->getProps()); + EXPECT_EQ(props.nativeId, "from-react"); + EXPECT_FLOAT_EQ(props.opacity, 0.5); +} + } // namespace facebook::react From 2e01b7b0038f6f58c903193cfa8d39f61f5d4256 Mon Sep 17 00:00:00 2001 From: Gregory Moskaliuk Date: Wed, 11 Nov 2026 16:53:49 +0100 Subject: [PATCH 2/5] Keep the props that include setNativeProps props in the family Review feedback: instead of a version counter on both ShadowNodeFamily and ShadowNode, `nativeProps_DEPRECATED` is now a pointer to a struct with the props and the props object they were last applied to. `ShadowNode::clone` skips parsing them when the node's `props_` is that object, so ShadowNode no longer needs a field of its own. `setNativeProps_DEPRECATED` and `UIManager::cloneNode` replace the struct, so the next clone applies the new native props. Co-Authored-By: Claude Opus 5.5 --- .../react/renderer/core/ShadowNode.cpp | 40 +++++++++---------- .../react/renderer/core/ShadowNode.h | 6 --- .../react/renderer/core/ShadowNodeFamily.h | 23 +++++++---- .../react/renderer/uimanager/UIManager.cpp | 28 +++++++------ .../uimanager/tests/FabricUIManagerTest.cpp | 15 +++++++ 5 files changed, 67 insertions(+), 45 deletions(-) diff --git a/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.cpp b/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.cpp index 20224379e120..f9e9d016aab0 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.cpp +++ b/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.cpp @@ -126,11 +126,6 @@ ShadowNode::ShadowNode( react_native_assert(children_); traits_.set(ShadowNodeTraits::Trait::ChildrenAreShared); - // A clone that keeps the source's props keeps the native props they include. - if (fragment.props == ShadowNodeFragment::propsPlaceholder()) { - nativePropsVersion_ = sourceShadowNode.nativePropsVersion_; - } - if (fragment.children) { for (const auto& child : *children_) { child->family_->setParent(family_); @@ -144,17 +139,15 @@ std::shared_ptr ShadowNode::clone( const auto& family = *family_; const auto& componentDescriptor = family.componentDescriptor_; - std::optional propsOverride; - uint32_t nativePropsVersion = 0; + std::shared_ptr nativeProps; { std::lock_guard lock(family.nativePropsMutex); if (family.nativeProps_DEPRECATED != nullptr) { if (fragment.props == ShadowNodeFragment::propsPlaceholder()) { - // Props of a node that already include the current - // `nativeProps_DEPRECATED` don't need them applied again. - if (nativePropsVersion_ != family.nativePropsVersion_DEPRECATED) { - propsOverride.emplace(*family.nativeProps_DEPRECATED); - nativePropsVersion = family.nativePropsVersion_DEPRECATED; + // Props that already include `nativeProps_DEPRECATED` don't need + // them applied again. + if (family.nativeProps_DEPRECATED->appliedProps != props_) { + nativeProps = family.nativeProps_DEPRECATED; } } else { // TODO: We might need to merge fragment.props with @@ -163,20 +156,27 @@ std::shared_ptr ShadowNode::clone( } } - if (propsOverride) { + if (nativeProps) { // Clone existing `props_` with `family.nativeProps_DEPRECATED` to - // apply previously set props via `setNativeProps` API. The parsed - // result escapes the lock so the rest of the clone work runs + // apply previously set props via `setNativeProps` API. The parsing + // runs outside the lock so the rest of the clone work runs // unblocked. auto propsParserContext = PropsParserContext{family_->getSurfaceId(), {}}; - auto clonedShadowNode = componentDescriptor.cloneShadowNode( + auto props = componentDescriptor.cloneProps( + propsParserContext, props_, RawProps(nativeProps->props)); + { + std::lock_guard lock(family.nativePropsMutex); + // Unless they changed in the meantime, the new props include the + // current `nativeProps_DEPRECATED`. + if (family.nativeProps_DEPRECATED == nativeProps) { + nativeProps->appliedProps = props; + } + } + return componentDescriptor.cloneShadowNode( *this, - {.props = componentDescriptor.cloneProps( - propsParserContext, props_, std::move(*propsOverride)), + {.props = props, .children = fragment.children, .state = fragment.state}); - clonedShadowNode->nativePropsVersion_ = nativePropsVersion; - return clonedShadowNode; } return componentDescriptor.cloneShadowNode(*this, fragment); diff --git a/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.h b/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.h index 7418a1e564bf..b21cb21b4f3b 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.h +++ b/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.h @@ -282,12 +282,6 @@ class ShadowNode : public Sealable, public DebugStringConvertible, public jsi::N */ mutable std::atomic hasBeenMounted_{false}; - /* - * The family's `nativePropsVersion_DEPRECATED` that `props_` already - * include, so that cloning doesn't apply `nativeProps_DEPRECATED` again. - */ - uint32_t nativePropsVersion_{0}; - static Props::Shared propsForClonedShadowNode(const ShadowNode &sourceShadowNode, const Props::Shared &props); protected: diff --git a/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamily.h b/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamily.h index 8a9ef564d596..4a93c91cf268 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamily.h +++ b/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamily.h @@ -19,6 +19,7 @@ namespace facebook::react { class ComponentDescriptor; +class Props; class ShadowNode; class State; @@ -122,20 +123,28 @@ class ShadowNodeFamily final : public jsi::NativeState { */ void dispatchRawState(StateUpdate &&stateUpdate, EventQueue::UpdateMode updateMode); + struct NativeProps_DEPRECATED { + /* + * Not modified once set: `nativeProps_DEPRECATED` is replaced instead. + */ + folly::dynamic props; + + /* + * The props of a shadow node that already include `props`, so that + * cloning that node doesn't apply them again. Guarded by + * `nativePropsMutex`. + */ + std::shared_ptr appliedProps; + }; + /* * Holds currently applied native props. `nullptr` if setNativeProps API is * not used. It is used to backport setNativeProps API from the old * architecture and will be removed in the future. */ - mutable std::unique_ptr nativeProps_DEPRECATED; + mutable std::shared_ptr nativeProps_DEPRECATED; mutable std::mutex nativePropsMutex; - /* - * Incremented whenever `nativeProps_DEPRECATED` changes, so a cloned node - * can tell whether its props already include them. - */ - mutable uint32_t nativePropsVersion_DEPRECATED{0}; - /** * @return tag for the ShadowNodeFamily. */ diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp index b12c41795780..404a32d97651 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp @@ -146,11 +146,12 @@ std::shared_ptr UIManager::cloneNode( // // The result of this operation is the new `nativeProps_DEPRECATED`. family.nativeProps_DEPRECATED = - std::make_unique(mergeDynamicProps( - *family.nativeProps_DEPRECATED, // source - (folly::dynamic)rawProps, // patch - NullValueStrategy::Ignore)); - family.nativePropsVersion_DEPRECATED++; + std::make_shared( + ShadowNodeFamily::NativeProps_DEPRECATED{ + .props = mergeDynamicProps( + family.nativeProps_DEPRECATED->props, // source + (folly::dynamic)rawProps, // patch + NullValueStrategy::Ignore)}); // 2. Compute the final set of props. // @@ -162,7 +163,7 @@ std::shared_ptr UIManager::cloneNode( // We use these finalProps as source of truth for the component. finalProps = mergeDynamicProps( (folly::dynamic)rawProps, // source - *family.nativeProps_DEPRECATED, // patch + family.nativeProps_DEPRECATED->props, // patch NullValueStrategy::Override); } } @@ -466,20 +467,23 @@ void UIManager::setNativeProps_DEPRECATED( auto& family = shadowNode->getFamily(); { std::lock_guard lock(family.nativePropsMutex); - family.nativePropsVersion_DEPRECATED++; if (family.nativeProps_DEPRECATED) { // Values in `rawProps` patch (take precedence over) // `nativeProps_DEPRECATED`. For example, if both // `nativeProps_DEPRECATED` and `rawProps` contain key 'A'. Value from // `rawProps` overrides what was previously in `nativeProps_DEPRECATED`. family.nativeProps_DEPRECATED = - std::make_unique(mergeDynamicProps( - *family.nativeProps_DEPRECATED, - (folly::dynamic)rawProps, - NullValueStrategy::Override)); + std::make_shared( + ShadowNodeFamily::NativeProps_DEPRECATED{ + .props = mergeDynamicProps( + family.nativeProps_DEPRECATED->props, + (folly::dynamic)rawProps, + NullValueStrategy::Override)}); } else { family.nativeProps_DEPRECATED = - std::make_unique((folly::dynamic)rawProps); + std::make_shared( + ShadowNodeFamily::NativeProps_DEPRECATED{ + .props = (folly::dynamic)rawProps}); } } diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/tests/FabricUIManagerTest.cpp b/packages/react-native/ReactCommon/react/renderer/uimanager/tests/FabricUIManagerTest.cpp index f8c96566adda..8ae4454f6c69 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/tests/FabricUIManagerTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/tests/FabricUIManagerTest.cpp @@ -178,6 +178,21 @@ TEST_F(FabricUIManagerTest, CloneAppliesSetNativePropsOnlyOnce) { static_cast(*third->getProps()).opacity, 0.25); } +// Another clone of a node whose props don't include the props set by +// `setNativeProps_DEPRECATED` still applies them, even after a clone of the +// same node already did. +TEST_F(FabricUIManagerTest, CloneAppliesSetNativePropsToEveryNodeWithoutThem) { + auto original = currentViewNode(); + uiManager_->setNativeProps_DEPRECATED( + original, RawProps(folly::dynamic::object("opacity", 0.5))); + + original->clone({}); + auto second = original->clone({}); + EXPECT_NE(second->getProps(), original->getProps()); + EXPECT_FLOAT_EQ( + static_cast(*second->getProps()).opacity, 0.5); +} + // After a React re-render replaces a node's props, the next clone applies the // props set by `setNativeProps_DEPRECATED` again. TEST_F(FabricUIManagerTest, CloneAppliesSetNativePropsAfterReactRender) { From 82c4383571e016409916a2b6c71745c499dfca55 Mon Sep 17 00:00:00 2001 From: Gregory Moskaliuk Date: Fri, 13 Nov 2026 12:37:35 +0100 Subject: [PATCH 3/5] Apply setNativeProps props in one locked block Co-Authored-By: Claude Opus 5.5 --- .../react/renderer/core/ShadowNode.cpp | 34 +++++++------------ 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.cpp b/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.cpp index f9e9d016aab0..7c7c8ded57d3 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.cpp +++ b/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.cpp @@ -139,15 +139,22 @@ std::shared_ptr ShadowNode::clone( const auto& family = *family_; const auto& componentDescriptor = family.componentDescriptor_; - std::shared_ptr nativeProps; + std::shared_ptr props; { std::lock_guard lock(family.nativePropsMutex); - if (family.nativeProps_DEPRECATED != nullptr) { + const auto& nativeProps = family.nativeProps_DEPRECATED; + if (nativeProps != nullptr) { if (fragment.props == ShadowNodeFragment::propsPlaceholder()) { // Props that already include `nativeProps_DEPRECATED` don't need // them applied again. - if (family.nativeProps_DEPRECATED->appliedProps != props_) { - nativeProps = family.nativeProps_DEPRECATED; + if (nativeProps->appliedProps != props_) { + // Clone existing `props_` with `family.nativeProps_DEPRECATED` to + // apply previously set props via `setNativeProps` API. + auto propsParserContext = + PropsParserContext{family_->getSurfaceId(), {}}; + props = componentDescriptor.cloneProps( + propsParserContext, props_, RawProps(nativeProps->props)); + nativeProps->appliedProps = props; } } else { // TODO: We might need to merge fragment.props with @@ -156,25 +163,10 @@ std::shared_ptr ShadowNode::clone( } } - if (nativeProps) { - // Clone existing `props_` with `family.nativeProps_DEPRECATED` to - // apply previously set props via `setNativeProps` API. The parsing - // runs outside the lock so the rest of the clone work runs - // unblocked. - auto propsParserContext = PropsParserContext{family_->getSurfaceId(), {}}; - auto props = componentDescriptor.cloneProps( - propsParserContext, props_, RawProps(nativeProps->props)); - { - std::lock_guard lock(family.nativePropsMutex); - // Unless they changed in the meantime, the new props include the - // current `nativeProps_DEPRECATED`. - if (family.nativeProps_DEPRECATED == nativeProps) { - nativeProps->appliedProps = props; - } - } + if (props) { return componentDescriptor.cloneShadowNode( *this, - {.props = props, + {.props = std::move(props), .children = fragment.children, .state = fragment.state}); } From 9c943fade7249fb350f8bc5d49edd7a9c1f0aaae Mon Sep 17 00:00:00 2001 From: Gregory Moskaliuk Date: Fri, 13 Nov 2026 16:25:36 +0100 Subject: [PATCH 4/5] Keep nativeProps_DEPRECATED a unique_ptr Co-Authored-By: Claude Opus 5.5 --- .../react/renderer/core/ShadowNodeFamily.h | 11 ++++------- .../react/renderer/uimanager/UIManager.cpp | 6 +++--- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamily.h b/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamily.h index 4a93c91cf268..ed6085718fdb 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamily.h +++ b/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamily.h @@ -124,15 +124,11 @@ class ShadowNodeFamily final : public jsi::NativeState { void dispatchRawState(StateUpdate &&stateUpdate, EventQueue::UpdateMode updateMode); struct NativeProps_DEPRECATED { - /* - * Not modified once set: `nativeProps_DEPRECATED` is replaced instead. - */ folly::dynamic props; /* * The props of a shadow node that already include `props`, so that - * cloning that node doesn't apply them again. Guarded by - * `nativePropsMutex`. + * cloning that node doesn't apply them again. */ std::shared_ptr appliedProps; }; @@ -140,9 +136,10 @@ class ShadowNodeFamily final : public jsi::NativeState { /* * Holds currently applied native props. `nullptr` if setNativeProps API is * not used. It is used to backport setNativeProps API from the old - * architecture and will be removed in the future. + * architecture and will be removed in the future. Guarded by + * `nativePropsMutex`. */ - mutable std::shared_ptr nativeProps_DEPRECATED; + mutable std::unique_ptr nativeProps_DEPRECATED; mutable std::mutex nativePropsMutex; /** diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp index 404a32d97651..a34b3592f1c1 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp @@ -146,7 +146,7 @@ std::shared_ptr UIManager::cloneNode( // // The result of this operation is the new `nativeProps_DEPRECATED`. family.nativeProps_DEPRECATED = - std::make_shared( + std::make_unique( ShadowNodeFamily::NativeProps_DEPRECATED{ .props = mergeDynamicProps( family.nativeProps_DEPRECATED->props, // source @@ -473,7 +473,7 @@ void UIManager::setNativeProps_DEPRECATED( // `nativeProps_DEPRECATED` and `rawProps` contain key 'A'. Value from // `rawProps` overrides what was previously in `nativeProps_DEPRECATED`. family.nativeProps_DEPRECATED = - std::make_shared( + std::make_unique( ShadowNodeFamily::NativeProps_DEPRECATED{ .props = mergeDynamicProps( family.nativeProps_DEPRECATED->props, @@ -481,7 +481,7 @@ void UIManager::setNativeProps_DEPRECATED( NullValueStrategy::Override)}); } else { family.nativeProps_DEPRECATED = - std::make_shared( + std::make_unique( ShadowNodeFamily::NativeProps_DEPRECATED{ .props = (folly::dynamic)rawProps}); } From dc41f3999b4bcdcea6dadb3db05cd05e1b2f82f2 Mon Sep 17 00:00:00 2001 From: Gregory Moskaliuk Date: Fri, 13 Nov 2026 17:58:41 +0100 Subject: [PATCH 5/5] Give NativeProps_DEPRECATED a constructor Co-Authored-By: Claude Opus 5.5 --- .../react/renderer/core/ShadowNodeFamily.h | 4 +++- .../react/renderer/uimanager/UIManager.cpp | 21 ++++++++----------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamily.h b/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamily.h index ed6085718fdb..eb1626ccae48 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamily.h +++ b/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamily.h @@ -124,13 +124,15 @@ class ShadowNodeFamily final : public jsi::NativeState { void dispatchRawState(StateUpdate &&stateUpdate, EventQueue::UpdateMode updateMode); struct NativeProps_DEPRECATED { + explicit NativeProps_DEPRECATED(folly::dynamic props) : props(std::move(props)) {} + folly::dynamic props; /* * The props of a shadow node that already include `props`, so that * cloning that node doesn't apply them again. */ - std::shared_ptr appliedProps; + std::shared_ptr appliedProps = nullptr; }; /* diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp index a34b3592f1c1..6a18881fd844 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp @@ -147,11 +147,10 @@ std::shared_ptr UIManager::cloneNode( // The result of this operation is the new `nativeProps_DEPRECATED`. family.nativeProps_DEPRECATED = std::make_unique( - ShadowNodeFamily::NativeProps_DEPRECATED{ - .props = mergeDynamicProps( - family.nativeProps_DEPRECATED->props, // source - (folly::dynamic)rawProps, // patch - NullValueStrategy::Ignore)}); + mergeDynamicProps( + family.nativeProps_DEPRECATED->props, // source + (folly::dynamic)rawProps, // patch + NullValueStrategy::Ignore)); // 2. Compute the final set of props. // @@ -474,16 +473,14 @@ void UIManager::setNativeProps_DEPRECATED( // `rawProps` overrides what was previously in `nativeProps_DEPRECATED`. family.nativeProps_DEPRECATED = std::make_unique( - ShadowNodeFamily::NativeProps_DEPRECATED{ - .props = mergeDynamicProps( - family.nativeProps_DEPRECATED->props, - (folly::dynamic)rawProps, - NullValueStrategy::Override)}); + mergeDynamicProps( + family.nativeProps_DEPRECATED->props, + (folly::dynamic)rawProps, + NullValueStrategy::Override)); } else { family.nativeProps_DEPRECATED = std::make_unique( - ShadowNodeFamily::NativeProps_DEPRECATED{ - .props = (folly::dynamic)rawProps}); + (folly::dynamic)rawProps); } }