mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 00:20:04 +01:00
[Fizz] Unblock SuspenseList when prerendering (#33321)
There's an interesting case when a SuspenseList is partially prerendered but some of the completed boundaries are blocked by rows to be resumed. This handles it but just unblocking the future rows to avoid stalling. However, the correct semantics will need special handling in the postponed state.
This commit is contained in:
parent
3710c4d4f9
commit
f4041aa388
|
|
@ -29,6 +29,7 @@ let ReactDOM;
|
|||
let ReactDOMFizzServer;
|
||||
let ReactDOMFizzStatic;
|
||||
let Suspense;
|
||||
let SuspenseList;
|
||||
let container;
|
||||
let Scheduler;
|
||||
let act;
|
||||
|
|
@ -50,6 +51,7 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
|||
ReactDOMFizzServer = require('react-dom/server.browser');
|
||||
ReactDOMFizzStatic = require('react-dom/static.browser');
|
||||
Suspense = React.Suspense;
|
||||
SuspenseList = React.unstable_SuspenseList;
|
||||
container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
});
|
||||
|
|
@ -2242,4 +2244,85 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
|||
</html>,
|
||||
);
|
||||
});
|
||||
|
||||
// @gate enableHalt && enableSuspenseList
|
||||
it('can resume a partially prerendered SuspenseList', async () => {
|
||||
const errors = [];
|
||||
|
||||
let resolveA;
|
||||
const promiseA = new Promise(r => (resolveA = r));
|
||||
let resolveB;
|
||||
const promiseB = new Promise(r => (resolveB = r));
|
||||
|
||||
async function ComponentA() {
|
||||
await promiseA;
|
||||
return 'A';
|
||||
}
|
||||
|
||||
async function ComponentB() {
|
||||
await promiseB;
|
||||
return 'B';
|
||||
}
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div>
|
||||
<SuspenseList revealOrder="forwards">
|
||||
<Suspense fallback="Loading A">
|
||||
<ComponentA />
|
||||
</Suspense>
|
||||
<Suspense fallback="Loading B">
|
||||
<ComponentB />
|
||||
</Suspense>
|
||||
<Suspense fallback="Loading C">C</Suspense>
|
||||
</SuspenseList>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const controller = new AbortController();
|
||||
const pendingResult = serverAct(() =>
|
||||
ReactDOMFizzStatic.prerender(<App />, {
|
||||
signal: controller.signal,
|
||||
onError(x) {
|
||||
errors.push(x.message);
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
await serverAct(() => {
|
||||
controller.abort();
|
||||
});
|
||||
|
||||
const prerendered = await pendingResult;
|
||||
const postponedState = JSON.stringify(prerendered.postponed);
|
||||
|
||||
await readIntoContainer(prerendered.prelude);
|
||||
expect(getVisibleChildren(container)).toEqual(
|
||||
<div>
|
||||
{'Loading A'}
|
||||
{'Loading B'}
|
||||
{'C' /* TODO: This should not be resolved. */}
|
||||
</div>,
|
||||
);
|
||||
|
||||
expect(prerendered.postponed).not.toBe(null);
|
||||
|
||||
await resolveA();
|
||||
await resolveB();
|
||||
|
||||
const dynamic = await serverAct(() =>
|
||||
ReactDOMFizzServer.resume(<App />, JSON.parse(postponedState)),
|
||||
);
|
||||
|
||||
await readIntoContainer(dynamic);
|
||||
|
||||
expect(getVisibleChildren(container)).toEqual(
|
||||
<div>
|
||||
{'A'}
|
||||
{'B'}
|
||||
{'C'}
|
||||
</div>,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
7
packages/react-server/src/ReactFizzServer.js
vendored
7
packages/react-server/src/ReactFizzServer.js
vendored
|
|
@ -4938,6 +4938,13 @@ function finishedTask(
|
|||
// preparation work during the work phase rather than the when flushing.
|
||||
preparePreamble(request);
|
||||
}
|
||||
} else if (boundary.status === POSTPONED) {
|
||||
const boundaryRow = boundary.row;
|
||||
if (boundaryRow !== null) {
|
||||
if (--boundaryRow.pendingTasks === 0) {
|
||||
finishSuspenseListRow(request, boundaryRow);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (segment !== null && segment.parentFlushed) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user