diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzStaticBrowser-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzStaticBrowser-test.js index 55a89bc525a3c..3d82300e8468e 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFizzStaticBrowser-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFizzStaticBrowser-test.js @@ -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', () => { , ); }); + + // @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 ( +
+ + + + + + + + C + +
+ ); + } + + const controller = new AbortController(); + const pendingResult = serverAct(() => + ReactDOMFizzStatic.prerender(, { + 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( +
+ {'Loading A'} + {'Loading B'} + {'C' /* TODO: This should not be resolved. */} +
, + ); + + expect(prerendered.postponed).not.toBe(null); + + await resolveA(); + await resolveB(); + + const dynamic = await serverAct(() => + ReactDOMFizzServer.resume(, JSON.parse(postponedState)), + ); + + await readIntoContainer(dynamic); + + expect(getVisibleChildren(container)).toEqual( +
+ {'A'} + {'B'} + {'C'} +
, + ); + }); }); diff --git a/packages/react-server/src/ReactFizzServer.js b/packages/react-server/src/ReactFizzServer.js index 1ea4568e1e3b1..f3c70b66c064d 100644 --- a/packages/react-server/src/ReactFizzServer.js +++ b/packages/react-server/src/ReactFizzServer.js @@ -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) {