From ff79b8fe5b40f6d239564a0a15c5285faba4eb74 Mon Sep 17 00:00:00 2001 From: Samuel Susla Date: Thu, 10 Nov 2022 13:34:15 +0000 Subject: [PATCH 1/2] Don't disappear layout effects unnecessarily Nested Offscreens can run into a case where anscestor Offscreen is revealed while inner one is hidden. This is an edge case that was previously missed. We need to prevent call to disappear layout effects. Check unit tests for an example of this. --- .../src/ReactFiberCommitWork.new.js | 7 +- .../src/ReactFiberCommitWork.old.js | 7 +- .../src/__tests__/ReactOffscreen-test.js | 92 ++++++++++++++----- 3 files changed, 75 insertions(+), 31 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.new.js b/packages/react-reconciler/src/ReactFiberCommitWork.new.js index 0b885bf13488b..1a9b72c8aeb0b 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.new.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.new.js @@ -2880,12 +2880,13 @@ function commitMutationEffectsOnFiber( if (isHidden) { const isUpdate = current !== null; - const isAncestorOffscreenHidden = offscreenSubtreeIsHidden; + const wasHiddenByAncestorOffscreen = + offscreenSubtreeIsHidden || offscreenSubtreeWasHidden; // Only trigger disapper layout effects if: // - This is an update, not first mount. // - This Offscreen was not hidden before. - // - No ancestor Offscreen is hidden. - if (isUpdate && !wasHidden && !isAncestorOffscreenHidden) { + // - Ancestor Offscreen was not hidden in previous commit. + if (isUpdate && !wasHidden && !wasHiddenByAncestorOffscreen) { if ((finishedWork.mode & ConcurrentMode) !== NoMode) { // Disappear the layout effects of all the children recursivelyTraverseDisappearLayoutEffects(finishedWork); diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.old.js b/packages/react-reconciler/src/ReactFiberCommitWork.old.js index cbbca982127f0..c1824d903453e 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.old.js @@ -2880,12 +2880,13 @@ function commitMutationEffectsOnFiber( if (isHidden) { const isUpdate = current !== null; - const isAncestorOffscreenHidden = offscreenSubtreeIsHidden; + const wasHiddenByAncestorOffscreen = + offscreenSubtreeIsHidden || offscreenSubtreeWasHidden; // Only trigger disapper layout effects if: // - This is an update, not first mount. // - This Offscreen was not hidden before. - // - No ancestor Offscreen is hidden. - if (isUpdate && !wasHidden && !isAncestorOffscreenHidden) { + // - Ancestor Offscreen was not hidden for previous pass. + if (isUpdate && !wasHidden && !wasHiddenByAncestorOffscreen) { if ((finishedWork.mode & ConcurrentMode) !== NoMode) { // Disappear the layout effects of all the children recursivelyTraverseDisappearLayoutEffects(finishedWork); diff --git a/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js b/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js index 417f851ad45b7..d830265610823 100644 --- a/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js +++ b/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js @@ -275,7 +275,7 @@ describe('ReactOffscreen', () => { // goes from visible to hidden in synchronous update. class ClassComponent extends React.Component { render() { - return ; + return ; } componentWillUnmount() { @@ -287,47 +287,89 @@ describe('ReactOffscreen', () => { } } - let _setIsVisible; - let isFirstRender = true; + const root = ReactNoop.createRoot(); + await act(async () => { + // Outer and inner offscreen are hidden. + root.render( + + + + + , + ); + }); - function App() { - const [isVisible, setIsVisible] = React.useState(true); - _setIsVisible = setIsVisible; - if (isFirstRender === true) { - setIsVisible(false); - isFirstRender = false; - } + expect(Scheduler).toHaveYielded(['child']); + expect(root).toMatchRenderedOutput(