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(
+
+
+
+
+ ,
+ );
+ });
+ });
});