Skip to content

Commit 3523be2

Browse files
committed
Prevent infinite re-render in StrictMode + Offscreen
1 parent a9dc73c commit 3523be2

File tree

3 files changed

+52
-2
lines changed

3 files changed

+52
-2
lines changed

packages/react-reconciler/src/ReactFiberWorkLoop.new.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ import {
124124
LayoutMask,
125125
PassiveMask,
126126
PlacementDEV,
127+
Visibility,
127128
} from './ReactFiberFlags';
128129
import {
129130
NoLanes,
@@ -3184,9 +3185,12 @@ function doubleInvokeEffectsInDEV(
31843185
) {
31853186
const isStrictModeFiber = fiber.type === REACT_STRICT_MODE_TYPE;
31863187
const isInStrictMode = parentIsInStrictMode || isStrictModeFiber;
3188+
31873189
if (fiber.flags & PlacementDEV || fiber.tag === OffscreenComponent) {
31883190
setCurrentDebugFiberInDEV(fiber);
3189-
if (isInStrictMode) {
3191+
const hasOffscreenVisibilityFlag =
3192+
fiber.tag !== OffscreenComponent || fiber.flags & Visibility;
3193+
if (isInStrictMode && hasOffscreenVisibilityFlag) {
31903194
disappearLayoutEffects(fiber);
31913195
disconnectPassiveEffect(fiber);
31923196
reappearLayoutEffects(root, fiber.alternate, fiber, false);

packages/react-reconciler/src/ReactFiberWorkLoop.old.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ import {
124124
LayoutMask,
125125
PassiveMask,
126126
PlacementDEV,
127+
Visibility,
127128
} from './ReactFiberFlags';
128129
import {
129130
NoLanes,
@@ -3184,9 +3185,12 @@ function doubleInvokeEffectsInDEV(
31843185
) {
31853186
const isStrictModeFiber = fiber.type === REACT_STRICT_MODE_TYPE;
31863187
const isInStrictMode = parentIsInStrictMode || isStrictModeFiber;
3188+
31873189
if (fiber.flags & PlacementDEV || fiber.tag === OffscreenComponent) {
31883190
setCurrentDebugFiberInDEV(fiber);
3189-
if (isInStrictMode) {
3191+
const hasOffscreenVisibilityFlag =
3192+
fiber.tag !== OffscreenComponent || fiber.flags & Visibility;
3193+
if (isInStrictMode && hasOffscreenVisibilityFlag) {
31903194
disappearLayoutEffects(fiber);
31913195
disconnectPassiveEffect(fiber);
31923196
reappearLayoutEffects(root, fiber.alternate, fiber, false);

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

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,21 @@ describe('ReactOffscreenStrictMode', () => {
7171

7272
log = [];
7373

74+
act(() => {
75+
ReactNoop.render(
76+
<React.StrictMode>
77+
<Offscreen mode="hidden">
78+
<Component label="A" />
79+
<Component label="B" />
80+
</Offscreen>
81+
</React.StrictMode>,
82+
);
83+
});
84+
85+
expect(log).toEqual(['A: render', 'A: render', 'B: render', 'B: render']);
86+
87+
log = [];
88+
7489
act(() => {
7590
ReactNoop.render(
7691
<React.StrictMode>
@@ -92,4 +107,31 @@ describe('ReactOffscreenStrictMode', () => {
92107
'A: useEffect mount',
93108
]);
94109
});
110+
111+
it('should not cause infinite render loop when StrictMode is used with Suspense and synchronous set states', () => {
112+
// This is a regression test, see https://github.com/facebook/react/pull/25179 for more details.
113+
function App() {
114+
const [state, setState] = React.useState(false);
115+
116+
React.useLayoutEffect(() => {
117+
setState(true);
118+
}, []);
119+
120+
React.useEffect(() => {
121+
// Empty useEffect with empty dependency array is needed to trigger infinite render loop.
122+
}, []);
123+
124+
return state;
125+
}
126+
127+
act(() => {
128+
ReactNoop.render(
129+
<React.StrictMode>
130+
<React.Suspense>
131+
<App />
132+
</React.Suspense>
133+
</React.StrictMode>,
134+
);
135+
});
136+
});
95137
});

0 commit comments

Comments
 (0)