fix bug in reparenting where wrong parentTag is used in differentiator (#50884)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/50884

changelog: [internal]

Fix incorrect parentTag coming from differentiator when reparenting. The fix is hidden behind existing feature flag that is already fixing similar issue: D73428312

The problem occurs under very specific circumstances that are covered by an integration test. The *parentTag* is also [only used on Android](https://github.com/facebook/react-native/blob/main/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.cpp#L566) and only if layout changes as part of the update.

This diff introduces a new test specifically triggering the incorrect behaviour: `Differentiator-itest.js`. Without this fix, the test fails on following assert:           [react_native_assert(hasTag(mutation.parentTag))](https://github.com/facebook/react-native/blob/main/packages/react-native/ReactCommon/react/renderer/mounting/stubs/StubViewTree.cpp#L245).

Reviewed By: mdvacca

Differential Revision: D73545165

fbshipit-source-id: 43344eebfa4cc0119e5b42170f6b9097dacd704d
This commit is contained in:
Samuel Susla
2025-04-23 20:21:49 -07:00
committed by Facebook GitHub Bot
parent d28d4c5ff8
commit f51d6ec9ff
2 changed files with 62 additions and 1 deletions
@@ -711,6 +711,11 @@ static void calculateShadowViewMutationsFlattener(
// Unflatten parent, flatten child
if (childReparentMode == ReparentMode::Flatten) {
auto parentTagForUpdateWhenUnflattened =
ReactNativeFeatureFlags::
enableFixForParentTagDuringReparenting()
? newTreeNodePair.shadowView.tag
: parentTag;
// Flatten old tree into new list
// At the end of this loop we still want to know which of these
// children are visited, so we reuse the `newRemainingPairs` map.
@@ -727,7 +732,7 @@ static void calculateShadowViewMutationsFlattener(
oldTreeNodePair,
(reparentMode == ReparentMode::Flatten
? oldTreeNodePair.shadowView.tag
: parentTag),
: parentTagForUpdateWhenUnflattened),
subVisitedNewMap,
subVisitedOldMap,
adjustedOldCullingContext,
@@ -295,6 +295,62 @@ test('parent-child switching from unflattened-flattened to flattened-unflattened
]);
});
test('parent-child switching from flattened-unflattened to unflattened-flattened', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(
<View
style={{
marginTop: 100,
}}>
<View
style={{
marginTop: 50,
opacity: 0,
}}>
<View nativeID={'child'} style={{height: 10, width: 10}} />
</View>
</View>,
);
});
expect(root.takeMountingManagerLogs()).toEqual([
'Update {type: "RootView", nativeID: (root)}',
'Create {type: "View", nativeID: (N/A)}',
'Create {type: "View", nativeID: "child"}',
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
'Insert {type: "View", parentNativeID: (root), index: 0, nativeID: (N/A)}',
]);
// force view to be flattened.
Fantom.runTask(() => {
root.render(
<View
style={{
marginTop: 100,
opacity: 0,
}}>
<View
style={{
marginTop: 50,
}}>
<View nativeID={'child'} style={{height: 10, width: 10}} />
</View>
</View>,
);
});
expect(root.takeMountingManagerLogs()).toEqual([
'Update {type: "View", nativeID: "child"}',
'Remove {type: "View", parentNativeID: (root), index: 0, nativeID: (N/A)}',
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
'Delete {type: "View", nativeID: (N/A)}',
'Create {type: "View", nativeID: (N/A)}',
'Insert {type: "View", parentNativeID: (root), index: 0, nativeID: (N/A)}',
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
]);
});
describe('reconciliation of setNativeProps and React commit', () => {
it('props set by setNativeProps must not be overriden by React commit', () => {
const root = Fantom.createRoot();