diff --git a/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.cpp b/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.cpp index e257f39e079c..44b0f8f468f3 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.cpp +++ b/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.cpp @@ -77,6 +77,7 @@ ShadowNode::ShadowNode( react_native_assert(children_); traits_.set(ShadowNodeTraits::Trait::ChildrenAreShared); + traits_.set(fragment.traits.get()); for (const auto& child : *children_) { child->family_->setParent(family_); @@ -106,7 +107,11 @@ ShadowNode::ShadowNode( react_native_assert(props_); react_native_assert(children_); + // State could have been progressed above by checking + // `sourceShadowNode.getMostRecentState()`. + traits_.unset(ShadowNodeTraits::Trait::ClonedByNativeStateUpdate); traits_.set(ShadowNodeTraits::Trait::ChildrenAreShared); + traits_.set(fragment.traits.get()); if (fragment.children) { for (const auto& child : *children_) { @@ -128,11 +133,10 @@ ShadowNode::Unshared ShadowNode::clone( propsParserContext, props_, RawProps(*family.nativeProps_DEPRECATED)); auto clonedNode = componentDescriptor.cloneShadowNode( *this, - { - props, - fragment.children, - fragment.state, - }); + {.props = props, + .children = fragment.children, + .state = fragment.state, + .traits = fragment.traits}); return clonedNode; } else { // TODO: We might need to merge fragment.priops with @@ -303,7 +307,8 @@ const ShadowNodeFamily& ShadowNode::getFamily() const { ShadowNode::Unshared ShadowNode::cloneTree( const ShadowNodeFamily& shadowNodeFamily, const std::function& - callback) const { + callback, + ShadowNodeTraits traits) const { auto ancestors = shadowNodeFamily.getAncestors(*this); if (ancestors.empty()) { @@ -330,10 +335,9 @@ ShadowNode::Unshared ShadowNode::cloneTree( ShadowNode::sameFamily(*children.at(childIndex), *childNode)); children[childIndex] = childNode; - childNode = parentNode.clone({ - ShadowNodeFragment::propsPlaceholder(), - std::make_shared(children), - }); + childNode = parentNode.clone( + {.children = std::make_shared(children), + .traits = traits}); } return std::const_pointer_cast(childNode); diff --git a/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.h b/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.h index 74f08707b12c..23b6a9d2b5fd 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.h +++ b/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.h @@ -100,8 +100,8 @@ class ShadowNode : public Sealable, */ Unshared cloneTree( const ShadowNodeFamily& shadowNodeFamily, - const std::function& callback) - const; + const std::function& callback, + ShadowNodeTraits traits = {}) const; #pragma mark - Getters diff --git a/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFragment.h b/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFragment.h index 66ea8cdc0d52..a521feac02a4 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFragment.h +++ b/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFragment.h @@ -26,6 +26,7 @@ struct ShadowNodeFragment { const Props::Shared& props = propsPlaceholder(); const ShadowNode::SharedListOfShared& children = childrenPlaceholder(); const State::Shared& state = statePlaceholder(); + const ShadowNodeTraits traits = {}; /* * Placeholders. diff --git a/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeTraits.h b/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeTraits.h index c8cb72f5e5b2..325321e5b905 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeTraits.h +++ b/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeTraits.h @@ -70,6 +70,8 @@ class ShadowNodeTraits { // to be cloned before the first mutation. ChildrenAreShared = 1 << 8, + // Indicates that the node was cloned because of native state update. + ClonedByNativeStateUpdate = 1 << 9, }; /* diff --git a/packages/react-native/ReactCommon/react/renderer/core/tests/ShadowNodeTest.cpp b/packages/react-native/ReactCommon/react/renderer/core/tests/ShadowNodeTest.cpp index ac00541f1030..44411abc24f1 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/tests/ShadowNodeTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/core/tests/ShadowNodeTest.cpp @@ -212,6 +212,26 @@ TEST_F(ShadowNodeTest, handleCloneFunction) { EXPECT_EQ(nodeAB_->getProps(), nodeABClone->getProps()); } +TEST_F(ShadowNodeTest, handleCloningWithTraits) { + auto clonedWithoutTraits = nodeAB_->clone({}); + + EXPECT_FALSE(clonedWithoutTraits->getTraits().check( + ShadowNodeTraits::Trait::ClonedByNativeStateUpdate)); + + auto newTraits = ShadowNodeTraits(); + newTraits.set(ShadowNodeTraits::Trait::ClonedByNativeStateUpdate); + + auto clonedWithTraits = clonedWithoutTraits->clone({.traits = newTraits}); + + EXPECT_TRUE(clonedWithTraits->getTraits().check( + ShadowNodeTraits::Trait::ClonedByNativeStateUpdate)); + + auto clonedAgain = clonedWithTraits->clone({}); + + EXPECT_FALSE(clonedAgain->getTraits().check( + ShadowNodeTraits::Trait::ClonedByNativeStateUpdate)); +} + TEST_F(ShadowNodeTest, handleState) { auto family = componentDescriptor_.createFamily(ShadowNodeFamilyFragment{ /* .tag = */ 9, @@ -266,3 +286,34 @@ TEST_F(ShadowNodeTest, handleState) { { secondNode->setStateData(TestState()); }, "Attempt to mutate a sealed object."); } + +TEST_F(ShadowNodeTest, testCloneTree) { + auto& family = nodeABA_->getFamily(); + auto newTraits = ShadowNodeTraits(); + newTraits.set(ShadowNodeTraits::Trait::ClonedByNativeStateUpdate); + auto rootNode = nodeA_->cloneTree( + family, + [newTraits](ShadowNode const& oldShadowNode) { + return oldShadowNode.clone({.traits = newTraits}); + }, + newTraits); + + EXPECT_TRUE(rootNode->getTraits().check( + ShadowNodeTraits::Trait::ClonedByNativeStateUpdate)); + + EXPECT_FALSE(rootNode->getChildren()[0]->getTraits().check( + ShadowNodeTraits::Trait::ClonedByNativeStateUpdate)); + + auto const& firstLevelChild = *rootNode->getChildren()[1]; + + EXPECT_TRUE(firstLevelChild.getTraits().check( + ShadowNodeTraits::Trait::ClonedByNativeStateUpdate)); + + EXPECT_FALSE(firstLevelChild.getChildren()[1]->getTraits().check( + ShadowNodeTraits::Trait::ClonedByNativeStateUpdate)); + + auto const& secondLevelchild = *firstLevelChild.getChildren()[0]; + + EXPECT_TRUE(secondLevelchild.getTraits().check( + ShadowNodeTraits::Trait::ClonedByNativeStateUpdate)); +}