Skip to content

Commit 4b19a8e

Browse files
committed
Call cleanup of insertion effects when hidden
Insertion effects do not unmount when a subtree goes offscreen, this means we still have to traverse the subtree in that case. We could potentially use a new subtree bit flag to avoid the traversal if there's no insertion effects, but I'm not sure that's worth the tradeoff of using a new flag? Likely also fixes #26670
1 parent 206df66 commit 4b19a8e

File tree

2 files changed

+69
-43
lines changed

2 files changed

+69
-43
lines changed

packages/react-reconciler/src/ReactFiberCommitWork.js

Lines changed: 41 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1324,58 +1324,56 @@ function commitDeletionEffectsOnFiber(
13241324
case ForwardRef:
13251325
case MemoComponent:
13261326
case SimpleMemoComponent: {
1327-
if (!offscreenSubtreeWasHidden) {
1328-
const updateQueue: FunctionComponentUpdateQueue | null =
1329-
(deletedFiber.updateQueue: any);
1330-
if (updateQueue !== null) {
1331-
const lastEffect = updateQueue.lastEffect;
1332-
if (lastEffect !== null) {
1333-
const firstEffect = lastEffect.next;
1334-
1335-
let effect = firstEffect;
1336-
do {
1337-
const tag = effect.tag;
1338-
const inst = effect.inst;
1339-
const destroy = inst.destroy;
1340-
if (destroy !== undefined) {
1341-
if ((tag & HookInsertion) !== NoHookEffect) {
1327+
const updateQueue: FunctionComponentUpdateQueue | null =
1328+
(deletedFiber.updateQueue: any);
1329+
if (updateQueue !== null) {
1330+
const lastEffect = updateQueue.lastEffect;
1331+
if (lastEffect !== null) {
1332+
const firstEffect = lastEffect.next;
1333+
1334+
let effect = firstEffect;
1335+
do {
1336+
const tag = effect.tag;
1337+
const inst = effect.inst;
1338+
const destroy = inst.destroy;
1339+
if (destroy !== undefined) {
1340+
if ((tag & HookInsertion) !== NoHookEffect) {
1341+
inst.destroy = undefined;
1342+
safelyCallDestroy(
1343+
deletedFiber,
1344+
nearestMountedAncestor,
1345+
destroy,
1346+
);
1347+
} else if ((tag & HookLayout) !== NoHookEffect) {
1348+
if (enableSchedulingProfiler) {
1349+
markComponentLayoutEffectUnmountStarted(deletedFiber);
1350+
}
1351+
1352+
if (shouldProfile(deletedFiber)) {
1353+
startLayoutEffectTimer();
13421354
inst.destroy = undefined;
13431355
safelyCallDestroy(
13441356
deletedFiber,
13451357
nearestMountedAncestor,
13461358
destroy,
13471359
);
1348-
} else if ((tag & HookLayout) !== NoHookEffect) {
1349-
if (enableSchedulingProfiler) {
1350-
markComponentLayoutEffectUnmountStarted(deletedFiber);
1351-
}
1352-
1353-
if (shouldProfile(deletedFiber)) {
1354-
startLayoutEffectTimer();
1355-
inst.destroy = undefined;
1356-
safelyCallDestroy(
1357-
deletedFiber,
1358-
nearestMountedAncestor,
1359-
destroy,
1360-
);
1361-
recordLayoutEffectDuration(deletedFiber);
1362-
} else {
1363-
inst.destroy = undefined;
1364-
safelyCallDestroy(
1365-
deletedFiber,
1366-
nearestMountedAncestor,
1367-
destroy,
1368-
);
1369-
}
1360+
recordLayoutEffectDuration(deletedFiber);
1361+
} else {
1362+
inst.destroy = undefined;
1363+
safelyCallDestroy(
1364+
deletedFiber,
1365+
nearestMountedAncestor,
1366+
destroy,
1367+
);
1368+
}
13701369

1371-
if (enableSchedulingProfiler) {
1372-
markComponentLayoutEffectUnmountStopped();
1373-
}
1370+
if (enableSchedulingProfiler) {
1371+
markComponentLayoutEffectUnmountStopped();
13741372
}
13751373
}
1376-
effect = effect.next;
1377-
} while (effect !== firstEffect);
1378-
}
1374+
}
1375+
effect = effect.next;
1376+
} while (effect !== firstEffect);
13791377
}
13801378
}
13811379

packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2872,6 +2872,34 @@ describe('ReactHooksWithNoopRenderer', () => {
28722872
});
28732873
});
28742874

2875+
// @gate enableActivity
2876+
it('runs insertion effect cleanup when unmounting in Offscreen state', async () => {
2877+
function Logger(props) {
2878+
useInsertionEffect(() => {
2879+
Scheduler.log(`create`);
2880+
return () => {
2881+
Scheduler.log(`destroy`);
2882+
};
2883+
}, []);
2884+
return null;
2885+
}
2886+
2887+
const Activity = React.unstable_Activity;
2888+
await act(async () => {
2889+
ReactNoop.render(
2890+
<Activity mode="hidden">
2891+
<Logger name="hidden" />
2892+
</Activity>,
2893+
);
2894+
await waitForAll(['create']);
2895+
});
2896+
2897+
await act(async () => {
2898+
ReactNoop.render(null);
2899+
await waitForAll(['destroy']);
2900+
});
2901+
});
2902+
28752903
it('assumes insertion effect destroy function is either a function or undefined', async () => {
28762904
function App(props) {
28772905
useInsertionEffect(() => {

0 commit comments

Comments
 (0)