From 3523be2e402af8b8c0ccdac43fa14d98e56866a2 Mon Sep 17 00:00:00 2001 From: Samuel Susla Date: Wed, 7 Sep 2022 17:11:31 +0100 Subject: [PATCH] Prevent infinite re-render in StrictMode + Offscreen --- .../src/ReactFiberWorkLoop.new.js | 6 ++- .../src/ReactFiberWorkLoop.old.js | 6 ++- .../ReactOffscreenStrictMode-test.js | 42 +++++++++++++++++++ 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js index 229756596cc16..3c7e4e0d5062f 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js @@ -124,6 +124,7 @@ import { LayoutMask, PassiveMask, PlacementDEV, + Visibility, } from './ReactFiberFlags'; import { NoLanes, @@ -3184,9 +3185,12 @@ function doubleInvokeEffectsInDEV( ) { const isStrictModeFiber = fiber.type === REACT_STRICT_MODE_TYPE; const isInStrictMode = parentIsInStrictMode || isStrictModeFiber; + if (fiber.flags & PlacementDEV || fiber.tag === OffscreenComponent) { setCurrentDebugFiberInDEV(fiber); - if (isInStrictMode) { + const hasOffscreenVisibilityFlag = + fiber.tag !== OffscreenComponent || fiber.flags & Visibility; + if (isInStrictMode && hasOffscreenVisibilityFlag) { disappearLayoutEffects(fiber); disconnectPassiveEffect(fiber); reappearLayoutEffects(root, fiber.alternate, fiber, false); diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js index c4caf55518a86..89012a036b015 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js @@ -124,6 +124,7 @@ import { LayoutMask, PassiveMask, PlacementDEV, + Visibility, } from './ReactFiberFlags'; import { NoLanes, @@ -3184,9 +3185,12 @@ function doubleInvokeEffectsInDEV( ) { const isStrictModeFiber = fiber.type === REACT_STRICT_MODE_TYPE; const isInStrictMode = parentIsInStrictMode || isStrictModeFiber; + if (fiber.flags & PlacementDEV || fiber.tag === OffscreenComponent) { setCurrentDebugFiberInDEV(fiber); - if (isInStrictMode) { + const hasOffscreenVisibilityFlag = + fiber.tag !== OffscreenComponent || fiber.flags & Visibility; + if (isInStrictMode && hasOffscreenVisibilityFlag) { disappearLayoutEffects(fiber); disconnectPassiveEffect(fiber); reappearLayoutEffects(root, fiber.alternate, fiber, false); diff --git a/packages/react-reconciler/src/__tests__/ReactOffscreenStrictMode-test.js b/packages/react-reconciler/src/__tests__/ReactOffscreenStrictMode-test.js index f750a9b7ef0d6..170b17614e90c 100644 --- a/packages/react-reconciler/src/__tests__/ReactOffscreenStrictMode-test.js +++ b/packages/react-reconciler/src/__tests__/ReactOffscreenStrictMode-test.js @@ -71,6 +71,21 @@ describe('ReactOffscreenStrictMode', () => { log = []; + act(() => { + ReactNoop.render( + + + + + + , + ); + }); + + expect(log).toEqual(['A: render', 'A: render', 'B: render', 'B: render']); + + log = []; + act(() => { ReactNoop.render( @@ -92,4 +107,31 @@ describe('ReactOffscreenStrictMode', () => { 'A: useEffect mount', ]); }); + + it('should not cause infinite render loop when StrictMode is used with Suspense and synchronous set states', () => { + // This is a regression test, see https://github.com/facebook/react/pull/25179 for more details. + function App() { + const [state, setState] = React.useState(false); + + React.useLayoutEffect(() => { + setState(true); + }, []); + + React.useEffect(() => { + // Empty useEffect with empty dependency array is needed to trigger infinite render loop. + }, []); + + return state; + } + + act(() => { + ReactNoop.render( + + + + + , + ); + }); + }); });