Skip to content

Commit 82ee1ee

Browse files
committed
Double invoke effects in a subtree wrapped with StrictMode
1 parent e06c72f commit 82ee1ee

File tree

2 files changed

+75
-9
lines changed

2 files changed

+75
-9
lines changed

packages/react-reconciler/src/ReactFiberWorkLoop.js

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4079,16 +4079,20 @@ function flushRenderPhaseStrictModeWarningsInDEV() {
40794079
function recursivelyTraverseAndDoubleInvokeEffectsInDEV(
40804080
root: FiberRoot,
40814081
parentFiber: Fiber,
4082+
treeFlags: Flags,
40824083
isInStrictMode: boolean,
40834084
) {
4084-
if ((parentFiber.subtreeFlags & (PlacementDEV | Visibility)) === NoFlags) {
4085+
if (
4086+
((treeFlags | parentFiber.subtreeFlags) & (PlacementDEV | Visibility)) ===
4087+
NoFlags
4088+
) {
40854089
// Parent's descendants have already had effects double invoked.
40864090
// Early exit to avoid unnecessary tree traversal.
40874091
return;
40884092
}
40894093
let child = parentFiber.child;
40904094
while (child !== null) {
4091-
doubleInvokeEffectsInDEVIfNecessary(root, child, isInStrictMode);
4095+
doubleInvokeEffectsInDEVIfNecessary(root, child, treeFlags, isInStrictMode);
40924096
child = child.sibling;
40934097
}
40944098
}
@@ -4117,6 +4121,7 @@ function doubleInvokeEffectsOnFiber(
41174121
function doubleInvokeEffectsInDEVIfNecessary(
41184122
root: FiberRoot,
41194123
fiber: Fiber,
4124+
treeFlags: Flags,
41204125
parentIsInStrictMode: boolean,
41214126
) {
41224127
const isStrictModeFiber = fiber.type === REACT_STRICT_MODE_TYPE;
@@ -4125,7 +4130,7 @@ function doubleInvokeEffectsInDEVIfNecessary(
41254130
// First case: the fiber **is not** of type OffscreenComponent. No
41264131
// special rules apply to double invoking effects.
41274132
if (fiber.tag !== OffscreenComponent) {
4128-
if (fiber.flags & PlacementDEV) {
4133+
if ((treeFlags | fiber.flags) & PlacementDEV) {
41294134
if (isInStrictMode) {
41304135
runWithFiberInDEV(
41314136
fiber,
@@ -4134,14 +4139,15 @@ function doubleInvokeEffectsInDEVIfNecessary(
41344139
fiber,
41354140
(fiber.mode & NoStrictPassiveEffectsMode) === NoMode,
41364141
);
4142+
return;
41374143
}
4138-
} else {
4139-
recursivelyTraverseAndDoubleInvokeEffectsInDEV(
4140-
root,
4141-
fiber,
4142-
isInStrictMode,
4143-
);
41444144
}
4145+
recursivelyTraverseAndDoubleInvokeEffectsInDEV(
4146+
root,
4147+
fiber,
4148+
treeFlags | fiber.flags,
4149+
isInStrictMode,
4150+
);
41454151
return;
41464152
}
41474153

@@ -4162,6 +4168,7 @@ function doubleInvokeEffectsInDEVIfNecessary(
41624168
recursivelyTraverseAndDoubleInvokeEffectsInDEV,
41634169
root,
41644170
fiber,
4171+
treeFlags | fiber.flags,
41654172
isInStrictMode,
41664173
);
41674174
}
@@ -4185,6 +4192,7 @@ function commitDoubleInvokeEffectsInDEV(
41854192
recursivelyTraverseAndDoubleInvokeEffectsInDEV(
41864193
root,
41874194
root.current,
4195+
root.current.flags,
41884196
doubleInvokeEffects,
41894197
);
41904198
} else {

packages/react-reconciler/src/__tests__/StrictEffectsModeDefaults-test.internal.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,64 @@ describe('StrictEffectsMode defaults', () => {
240240
});
241241
});
242242

243+
it('should double invoke effects in a mounted strict subtree', async () => {
244+
const log = [];
245+
function ComponentWithEffects({label}) {
246+
React.useEffect(() => {
247+
log.push(`useEffect mount "${label}"`);
248+
Scheduler.log(`useEffect mount "${label}"`);
249+
return () => {
250+
log.push(`useEffect unmount "${label}"`);
251+
Scheduler.log(`useEffect unmount "${label}"`);
252+
};
253+
});
254+
255+
React.useLayoutEffect(() => {
256+
log.push(`useLayoutEffect mount "${label}"`);
257+
Scheduler.log(`useLayoutEffect mount "${label}"`);
258+
return () => {
259+
log.push(`useLayoutEffect unmount "${label}"`);
260+
Scheduler.log(`useLayoutEffect unmount "${label}"`);
261+
};
262+
});
263+
264+
return label;
265+
}
266+
267+
// this component intentionally introduces a component layer between the root and the StrictMode
268+
function RenderChildren({children}) {
269+
return children;
270+
}
271+
272+
await act(async () => {
273+
ReactNoop.render(
274+
<RenderChildren>
275+
<ComponentWithEffects label={'one'} />
276+
<React.StrictMode>
277+
<ComponentWithEffects label={'two'} />
278+
</React.StrictMode>
279+
</RenderChildren>,
280+
);
281+
282+
await waitForAll([
283+
'useLayoutEffect mount "one"',
284+
'useLayoutEffect mount "two"',
285+
'useEffect mount "one"',
286+
'useEffect mount "two"',
287+
]);
288+
expect(log).toEqual([
289+
'useLayoutEffect mount "one"',
290+
'useLayoutEffect mount "two"',
291+
'useEffect mount "one"',
292+
'useEffect mount "two"',
293+
'useLayoutEffect unmount "two"',
294+
'useEffect unmount "two"',
295+
'useLayoutEffect mount "two"',
296+
'useEffect mount "two"',
297+
]);
298+
});
299+
});
300+
243301
it('double invoking for effects for modern roots', async () => {
244302
const log = [];
245303
function App({text}) {

0 commit comments

Comments
 (0)