diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.new.js b/packages/react-reconciler/src/ReactFiberCommitWork.new.js index 40c3bd2b231bc..f3fa8fbb21552 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.new.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.new.js @@ -2880,7 +2880,8 @@ function commitMutationEffectsOnFiber( } if (isHidden) { - if (!wasHidden) { + // Check if this is an update, and the tree was previously visible. + if (current !== null && !wasHidden) { if ((offscreenBoundary.mode & ConcurrentMode) !== NoMode) { // Disappear the layout effects of all the children recursivelyTraverseDisappearLayoutEffects(offscreenBoundary); diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.old.js b/packages/react-reconciler/src/ReactFiberCommitWork.old.js index 4f960961c5e33..ea1ff57f3d8db 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.old.js @@ -2880,7 +2880,8 @@ function commitMutationEffectsOnFiber( } if (isHidden) { - if (!wasHidden) { + // Check if this is an update, and the tree was previously visible. + if (current !== null && !wasHidden) { if ((offscreenBoundary.mode & ConcurrentMode) !== NoMode) { // Disappear the layout effects of all the children recursivelyTraverseDisappearLayoutEffects(offscreenBoundary); diff --git a/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js b/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js index 66bd613763960..f7ca087088a70 100644 --- a/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js +++ b/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js @@ -268,6 +268,44 @@ describe('ReactOffscreen', () => { expect(root).toMatchRenderedOutput(); }); + // @gate enableOffscreen + it('nested offscreen does not call componentWillUnmount when hidden', async () => { + // This is a bug that appeared during production test of . + // It is a very specific scenario with nested Offscreens. The inner offscreen + // goes from visible to hidden in synchronous update. + class ClassComponent extends React.Component { + render() { + return ; + } + + componentWillUnmount() { + Scheduler.unstable_yieldValue('componentWillUnmount'); + } + } + + function App() { + const [isVisible, setIsVisible] = React.useState(true); + + if (isVisible === true) { + setIsVisible(false); + } + + return ( + + + + + + ); + } + + const root = ReactNoop.createRoot(); + await act(async () => { + root.render(); + }); + expect(Scheduler).toHaveYielded(['Child']); + }); + // @gate enableOffscreen it('mounts/unmounts layout effects when visibility changes (starting hidden)', async () => { function Child({text}) {