Skip to content

Commit 8ef9bb2

Browse files
committed
add a Cloned flag to indicate a required clone in persistent mode
1 parent 0066e0b commit 8ef9bb2

File tree

4 files changed

+64
-15
lines changed

4 files changed

+64
-15
lines changed

packages/react-native-renderer/src/__tests__/ReactFabric-test.internal.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,46 @@ describe('ReactFabric', () => {
273273
expect(nativeFabricUIManager.completeRoot).toBeCalled();
274274
});
275275

276+
it('should not clone nodes when layout effects are used', async () => {
277+
const View = createReactNativeComponentClass('RCTView', () => ({
278+
validAttributes: {foo: true},
279+
uiViewClassName: 'RCTView',
280+
}));
281+
282+
const ComponentWithEffect = () => {
283+
// Same thing happens with `ref` and `useImperativeHandle`
284+
React.useLayoutEffect(() => {});
285+
return null;
286+
};
287+
288+
await act(() =>
289+
ReactFabric.render(
290+
<View>
291+
<ComponentWithEffect />
292+
</View>,
293+
11,
294+
),
295+
);
296+
expect(nativeFabricUIManager.completeRoot).toBeCalled();
297+
jest.clearAllMocks();
298+
299+
await act(() =>
300+
ReactFabric.render(
301+
<View>
302+
<ComponentWithEffect />
303+
</View>,
304+
11,
305+
),
306+
);
307+
expect(nativeFabricUIManager.cloneNode).not.toBeCalled();
308+
expect(nativeFabricUIManager.cloneNodeWithNewChildren).not.toBeCalled();
309+
expect(nativeFabricUIManager.cloneNodeWithNewProps).not.toBeCalled();
310+
expect(
311+
nativeFabricUIManager.cloneNodeWithNewChildrenAndProps,
312+
).not.toBeCalled();
313+
expect(nativeFabricUIManager.completeRoot).not.toBeCalled();
314+
});
315+
276316
it('should call dispatchCommand for native refs', async () => {
277317
const View = createReactNativeComponentClass('RCTView', () => ({
278318
validAttributes: {foo: true},

packages/react-reconciler/src/ReactFiberCommitWork.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ import {
9797
Visibility,
9898
ShouldSuspendCommit,
9999
MaySuspendCommit,
100+
Cloned,
100101
} from './ReactFiberFlags';
101102
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
102103
import {
@@ -2522,7 +2523,7 @@ function recursivelyTraverseMutationEffects(
25222523
}
25232524

25242525
const prevDebugFiber = getCurrentDebugFiberInDEV();
2525-
if (parentFiber.subtreeFlags & MutationMask) {
2526+
if (parentFiber.subtreeFlags & (MutationMask | Cloned)) {
25262527
let child = parentFiber.child;
25272528
while (child !== null) {
25282529
setCurrentDebugFiberInDEV(child);

packages/react-reconciler/src/ReactFiberCompleteWork.js

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,12 @@ import {
8383
Snapshot,
8484
ChildDeletion,
8585
StaticMask,
86-
MutationMask,
8786
Passive,
8887
ForceClientRender,
8988
MaySuspendCommit,
9089
ScheduleRetry,
9190
ShouldSuspendCommit,
91+
Cloned,
9292
} from './ReactFiberFlags';
9393

9494
import {
@@ -182,6 +182,16 @@ function markUpdate(workInProgress: Fiber) {
182182
workInProgress.flags |= Update;
183183
}
184184

185+
/**
186+
* Tag the fiber with Cloned in persistent mode to signal that
187+
* it received an update that requires a clone of the tree above.
188+
*/
189+
function markCloned(workInProgress: Fiber) {
190+
if (supportsPersistence) {
191+
workInProgress.flags |= Cloned;
192+
}
193+
}
194+
185195
/**
186196
* In persistent mode, return whether this update needs to clone the subtree.
187197
*/
@@ -200,8 +210,8 @@ function doesRequireClone(current: null | Fiber, completedWork: Fiber) {
200210
let child = completedWork.child;
201211
while (child !== null) {
202212
if (
203-
(child.flags & MutationMask) !== NoFlags ||
204-
(child.subtreeFlags & MutationMask) !== NoFlags
213+
(child.flags & (Cloned | Visibility | Placement)) !== NoFlags ||
214+
(child.subtreeFlags & (Cloned | Visibility | Placement)) !== NoFlags
205215
) {
206216
return true;
207217
}
@@ -451,6 +461,7 @@ function updateHostComponent(
451461

452462
let newChildSet = null;
453463
if (requiresClone && passChildrenWhenCloningPersistedNodes) {
464+
markCloned(workInProgress);
454465
newChildSet = createContainerChildSet();
455466
// If children might have changed, we have to add them all to the set.
456467
appendAllChildrenToContainer(
@@ -474,6 +485,8 @@ function updateHostComponent(
474485
// Note that this might release a previous clone.
475486
workInProgress.stateNode = currentInstance;
476487
return;
488+
} else {
489+
markCloned(workInProgress);
477490
}
478491

479492
// Certain renderers require commit-time effects for initial mount.
@@ -485,13 +498,8 @@ function updateHostComponent(
485498
markUpdate(workInProgress);
486499
}
487500
workInProgress.stateNode = newInstance;
488-
if (!requiresClone) {
489-
// If there are no other effects in this tree, we need to flag this node as having one.
490-
// Even though we're not going to use it for anything.
491-
// Otherwise parents won't know that there are new children to propagate upwards.
492-
markUpdate(workInProgress);
493-
} else if (!passChildrenWhenCloningPersistedNodes) {
494-
// If children might have changed, we have to add them all to the set.
501+
if (requiresClone && !passChildrenWhenCloningPersistedNodes) {
502+
// If children have changed, we have to add them all to the set.
495503
appendAllChildren(
496504
newInstance,
497505
workInProgress,
@@ -650,15 +658,13 @@ function updateHostText(
650658
// If the text content differs, we'll create a new text instance for it.
651659
const rootContainerInstance = getRootHostContainer();
652660
const currentHostContext = getHostContext();
661+
markCloned(workInProgress);
653662
workInProgress.stateNode = createTextInstance(
654663
newText,
655664
rootContainerInstance,
656665
currentHostContext,
657666
workInProgress,
658667
);
659-
// We'll have to mark it as having an effect, even though we won't use the effect for anything.
660-
// This lets the parents know that at least one of their children has changed.
661-
markUpdate(workInProgress);
662668
} else {
663669
workInProgress.stateNode = current.stateNode;
664670
}
@@ -1254,6 +1260,7 @@ function completeWork(
12541260
);
12551261
// TODO: For persistent renderers, we should pass children as part
12561262
// of the initial instance creation
1263+
markCloned(workInProgress);
12571264
appendAllChildren(instance, workInProgress, false, false);
12581265
workInProgress.stateNode = instance;
12591266

@@ -1311,6 +1318,7 @@ function completeWork(
13111318
markUpdate(workInProgress);
13121319
}
13131320
} else {
1321+
markCloned(workInProgress);
13141322
workInProgress.stateNode = createTextInstance(
13151323
newText,
13161324
rootContainerInstance,

packages/react-reconciler/src/ReactFiberFlags.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export const Hydrating = /* */ 0b0000000000000001000000000000
2020

2121
// You can change the rest (and add more).
2222
export const Update = /* */ 0b0000000000000000000000000100;
23-
/* Skipped value: 0b0000000000000000000000001000; */
23+
export const Cloned = /* */ 0b0000000000000000000000001000;
2424

2525
export const ChildDeletion = /* */ 0b0000000000000000000000010000;
2626
export const ContentReset = /* */ 0b0000000000000000000000100000;

0 commit comments

Comments
 (0)