Preserve state when cloning the shadow tree using cloneTree
method
#44796
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Summary:
The current behavior when calling
cloneTree
is to not copy the state to theShadowNodeFragment
which results in the state being equal tostatePlaceholder
. Inside the relevantShadowNode
constructor, the state is checked and if no state was passed, the most recent mounted one is used for the new node:react-native/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.cpp
Lines 101 to 103 in a1e8118
mostRecentState
is updated during the first construction of a node in the family:react-native/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.cpp
Line 88 in a1e8118
and when the new node is mounted:
react-native/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.cpp
Lines 282 to 289 in a1e8118
This turns out to be a problem when using custom commit hooks which rely on the
cloneTree
method. Let's take the following case:where node
B
has a custom native state. Now let's say there is also a commit hook that will modify the nodeD
by callingcloneTree
with its family as the argument.In case there is a commit that would modify the native state of
B
.tryCommit
method would be called and would generate a new tree whereB
has the new state. The commit hook would also trigger on the new tree resulting in the entire tree being cloned to modify theD
node in some way. The problem is that it would clone the entire path toD
(soA
,B
, andC
) without setting the state in the fragment used to clone them, because of that the state used to clone them the second time would be the most recently committed one. Now, the new state forB
node hasn't yet been committed as it happens on mount, so it wouldn't be used when cloning. This effectively means that the state update for a node is dropped when a commit hook tries to modify any of its descendants using thecloneTree
method.Change from this PR makes it so the state from the tree being cloned is used when cloning nodes instead of the most recently committed one. That behavior seems to make more sense to me since potentially modifying the state of nodes on the path to the actually cloned one seems like an unwanted side effect.
The same change has been already rolled out in Reanimated's customized cloning logic (for similar reasons) and we didn't observe any problems caused by it.
Related issue: Expensify/react-native-live-markdown#346
Changelog:
[GENERAL] [FIXED] - Preserve state from the shadow tree being cloned when calling
cloneTree
Test Plan:
I've tried it out on RNTestes and didn't see anything out of the ordinary, though I'm not sure whether this change may have some unintended consequences.