diff --git a/packages/react-native/Libraries/Components/ScrollView/__tests__/ScrollView-viewCulling-itest.js b/packages/react-native/Libraries/Components/ScrollView/__tests__/ScrollView-viewCulling-itest.js
index d7536bb4104..43be9bac989 100644
--- a/packages/react-native/Libraries/Components/ScrollView/__tests__/ScrollView-viewCulling-itest.js
+++ b/packages/react-native/Libraries/Components/ScrollView/__tests__/ScrollView-viewCulling-itest.js
@@ -2327,6 +2327,126 @@ describe('reparenting', () => {
'Insert {type: "View", parentNativeID: "grandchild", index: 0, nativeID: "grandgrandchild"}',
]);
});
+
+ test('parent-child flattening with deep hierarchy', () => {
+ function renderTree(root: Fantom.Root, isFinal: boolean) {
+ Fantom.runTask(() => {
+ root.render(
+
+
+
+
+
+
+
+
+
+
+ ,
+ );
+ });
+ }
+
+ const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
+
+ renderTree(root, false);
+
+ expect(root.takeMountingManagerLogs()).not.toContain(
+ 'Create {type: "View", nativeID: "child"}',
+ );
+
+ renderTree(root, true);
+
+ expect(root.takeMountingManagerLogs()).toContain(
+ 'Create {type: "View", nativeID: "child"}',
+ );
+
+ const finalRoot = Fantom.createRoot({
+ viewportWidth: 100,
+ viewportHeight: 100,
+ });
+
+ renderTree(finalRoot, true);
+
+ expect(root.getRenderedOutput().toJSON).toEqual(
+ finalRoot.getRenderedOutput().toJSON,
+ );
+ });
+
+ test('parent-child unflattening with deep hierarchy', () => {
+ function renderTree(root: Fantom.Root, isFinal: boolean) {
+ Fantom.runTask(() => {
+ root.render(
+
+
+
+
+
+
+
+
+
+
+ ,
+ );
+ });
+ }
+
+ const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
+
+ renderTree(root, false);
+
+ expect(root.takeMountingManagerLogs()).not.toContain(
+ 'Create {type: "View", nativeID: "child"}',
+ );
+
+ renderTree(root, true);
+
+ expect(root.takeMountingManagerLogs()).toContain(
+ 'Create {type: "View", nativeID: "child"}',
+ );
+
+ const finalRoot = Fantom.createRoot({
+ viewportWidth: 100,
+ viewportHeight: 100,
+ });
+
+ renderTree(finalRoot, true);
+
+ expect(root.getRenderedOutput().toJSON).toEqual(
+ finalRoot.getRenderedOutput().toJSON,
+ );
+ });
});
describe('opt out mechanism - Unstable_uncullableView & Unstable_uncullableTrace', () => {
diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/Differentiator.cpp b/packages/react-native/ReactCommon/react/renderer/mounting/Differentiator.cpp
index 6aa22194ad1..3c032efbfba 100644
--- a/packages/react-native/ReactCommon/react/renderer/mounting/Differentiator.cpp
+++ b/packages/react-native/ReactCommon/react/renderer/mounting/Differentiator.cpp
@@ -630,7 +630,8 @@ static void calculateShadowViewMutationsFlattener(
// Update children if appropriate.
if (!oldTreeNodePair.flattened && !newTreeNodePair.flattened) {
- if (oldTreeNodePair.shadowNode != newTreeNodePair.shadowNode) {
+ if (oldTreeNodePair.shadowNode != newTreeNodePair.shadowNode ||
+ adjustedOldCullingContext != adjustedNewCullingContext) {
ViewNodePairScope innerScope{};
auto oldGrandChildPairs =
sliceChildShadowNodeViewPairsFromViewNodePair(
@@ -678,7 +679,7 @@ static void calculateShadowViewMutationsFlattener(
: parentTag),
subVisitedNewMap,
subVisitedOldMap,
- cullingContext,
+ cullingContextForUnvisitedOtherNodes,
cullingContext.adjustCullingContextIfNeeded(treeChildPair));
} else {
// Get flattened nodes from either new or old tree