Skip to content

Commit 235a6c4

Browse files
authored
Bugfix: Dropped effects in Legacy Mode Suspense (#18238)
* Failing: Dropped effects in Legacy Mode Suspense * Transfer mounted effects on suspend in legacy mode In legacy mode, a component that suspends bails out and commit in its previous state. If the component previously had mounted effects, we must transfer those to the work-in-progress so they don't get dropped.
1 parent 5fbb165 commit 235a6c4

File tree

4 files changed

+78
-0
lines changed

4 files changed

+78
-0
lines changed

packages/react-noop-renderer/src/ReactNoop.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const {
2424
getOrCreateRootContainer,
2525
createRoot,
2626
createBlockingRoot,
27+
createLegacyRoot,
2728
getChildrenAsJSX,
2829
getPendingChildrenAsJSX,
2930
createPortal,

packages/react-noop-renderer/src/createReactNoop.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -785,6 +785,32 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
785785
};
786786
},
787787

788+
createLegacyRoot() {
789+
const container = {
790+
rootID: '' + idCounter++,
791+
pendingChildren: [],
792+
children: [],
793+
};
794+
const fiberRoot = NoopRenderer.createContainer(
795+
container,
796+
LegacyRoot,
797+
false,
798+
null,
799+
);
800+
return {
801+
_Scheduler: Scheduler,
802+
render(children: ReactNodeList) {
803+
NoopRenderer.updateContainer(children, fiberRoot, null, null);
804+
},
805+
getChildren() {
806+
return getChildren(container);
807+
},
808+
getChildrenAsJSX() {
809+
return getChildrenAsJSX(container);
810+
},
811+
};
812+
},
813+
788814
getChildrenAsJSX(rootID: string = DEFAULT_ROOT_ID) {
789815
const container = rootContainers.get(rootID);
790816
return getChildrenAsJSX(container);

packages/react-reconciler/src/ReactFiberThrow.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,9 +199,11 @@ function throwException(
199199
// to render it.
200200
let currentSource = sourceFiber.alternate;
201201
if (currentSource) {
202+
sourceFiber.updateQueue = currentSource.updateQueue;
202203
sourceFiber.memoizedState = currentSource.memoizedState;
203204
sourceFiber.expirationTime = currentSource.expirationTime;
204205
} else {
206+
sourceFiber.updateQueue = null;
205207
sourceFiber.memoizedState = null;
206208
}
207209
}

packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1448,6 +1448,55 @@ function loadModules({
14481448
'Caught an error: Error in host config.',
14491449
);
14501450
});
1451+
1452+
it('does not drop mounted effects', async () => {
1453+
let never = {then() {}};
1454+
1455+
let setShouldSuspend;
1456+
function App() {
1457+
const [shouldSuspend, _setShouldSuspend] = React.useState(0);
1458+
setShouldSuspend = _setShouldSuspend;
1459+
return (
1460+
<Suspense fallback="Loading...">
1461+
<Child shouldSuspend={shouldSuspend} />
1462+
</Suspense>
1463+
);
1464+
}
1465+
1466+
function Child({shouldSuspend}) {
1467+
if (shouldSuspend) {
1468+
throw never;
1469+
}
1470+
1471+
React.useEffect(() => {
1472+
Scheduler.unstable_yieldValue('Mount');
1473+
return () => {
1474+
Scheduler.unstable_yieldValue('Unmount');
1475+
};
1476+
}, []);
1477+
1478+
return 'Child';
1479+
}
1480+
1481+
const root = ReactNoop.createLegacyRoot(null);
1482+
await ReactNoop.act(async () => {
1483+
root.render(<App />);
1484+
});
1485+
expect(Scheduler).toHaveYielded(['Mount']);
1486+
expect(root).toMatchRenderedOutput('Child');
1487+
1488+
// Suspend the child. This puts it into an inconsistent state.
1489+
await ReactNoop.act(async () => {
1490+
setShouldSuspend(true);
1491+
});
1492+
expect(root).toMatchRenderedOutput('Loading...');
1493+
1494+
// Unmount everying
1495+
await ReactNoop.act(async () => {
1496+
root.render(null);
1497+
});
1498+
expect(Scheduler).toHaveYielded(['Unmount']);
1499+
});
14511500
});
14521501

14531502
it('does not call lifecycles of a suspended component', async () => {

0 commit comments

Comments
 (0)