diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzStaticBrowser-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzStaticBrowser-test.js index 948aaab41543b..70d8de5c19ff2 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFizzStaticBrowser-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFizzStaticBrowser-test.js @@ -1495,4 +1495,51 @@ describe('ReactDOMFizzStaticBrowser', () => { 'hello', ]); }); + + // @gate enablePostpone + it('can render a deep list of single components where one postpones', async () => { + let isPrerendering = true; + function Outer({children}) { + return children; + } + + function Middle({children}) { + return children; + } + + function Inner() { + if (isPrerendering) { + React.unstable_postpone(); + } + return 'hello'; + } + + function App() { + return ( + + + + + + + + ); + } + + const prerendered = await ReactDOMFizzStatic.prerender(); + const postponedState = JSON.stringify(prerendered.postponed); + + await readIntoContainer(prerendered.prelude); + expect(getVisibleChildren(container)).toEqual('loading...'); + + isPrerendering = false; + + const dynamic = await ReactDOMFizzServer.resume( + , + JSON.parse(postponedState), + ); + + await readIntoContainer(dynamic); + expect(getVisibleChildren(container)).toEqual('hello'); + }); }); diff --git a/packages/react-server/src/ReactFizzServer.js b/packages/react-server/src/ReactFizzServer.js index c2e6f289db93d..675b8816a7a0b 100644 --- a/packages/react-server/src/ReactFizzServer.js +++ b/packages/react-server/src/ReactFizzServer.js @@ -2542,15 +2542,22 @@ function trackPostpone( const children: Array = []; if (boundaryKeyPath === keyPath && task.childIndex === -1) { - // Since we postponed directly in the Suspense boundary we can't have written anything - // to its segment. Therefore this will end up becoming the root segment. - segment.id = boundary.rootSegmentID; + // Assign ID + if (segment.id === -1) { + if (segment.parentFlushed) { + // If this segment's parent was already flushed, it means we really just + // skipped the parent and this segment is now the root. + segment.id = boundary.rootSegmentID; + } else { + segment.id = request.nextSegmentId++; + } + } // We postponed directly inside the Suspense boundary so we mark this for resuming. const boundaryNode: ReplaySuspenseBoundary = [ boundaryKeyPath[1], boundaryKeyPath[2], children, - boundary.rootSegmentID, + segment.id, fallbackReplayNode, boundary.rootSegmentID, ]; @@ -3264,7 +3271,8 @@ function queueCompletedSegment( if ( segment.chunks.length === 0 && segment.children.length === 1 && - segment.children[0].boundary === null + segment.children[0].boundary === null && + segment.children[0].id === -1 ) { // This is an empty segment. There's nothing to write, so we can instead transfer the ID // to the child. That way any existing references point to the child.