diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzSuspenseList-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzSuspenseList-test.js index 90ced7d677dbb..3a2d854d33275 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFizzSuspenseList-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFizzSuspenseList-test.js @@ -324,4 +324,77 @@ describe('ReactDOMFizSuspenseList', () => { , ); }); + + // @gate enableSuspenseList + it('waits for a nested SuspenseList to complete before resolving "forwards"', async () => { + const A = createAsyncText('A'); + const B = createAsyncText('B'); + const C = createAsyncText('C'); + + function Foo() { + return ( +
+ + + }> + + + }> + + + + }> + + + +
+ ); + } + + await C.resolve(); + + await serverAct(async () => { + const {pipe} = ReactDOMFizzServer.renderToPipeableStream(); + pipe(writable); + }); + + assertLog([ + 'Suspend! [B]', + 'Suspend! [A]', + 'C', + 'Loading B', + 'Loading A', + 'Loading C', + ]); + + expect(getVisibleChildren(container)).toEqual( +
+ Loading A + Loading B + Loading C +
, + ); + + await serverAct(() => A.resolve()); + assertLog(['A']); + + expect(getVisibleChildren(container)).toEqual( +
+ Loading A + Loading B + Loading C +
, + ); + + await serverAct(() => B.resolve()); + assertLog(['B']); + + expect(getVisibleChildren(container)).toEqual( +
+ A + B + C +
, + ); + }); }); diff --git a/packages/react-server/src/ReactFizzServer.js b/packages/react-server/src/ReactFizzServer.js index b8f5c1be75db0..70e61f42c6e8e 100644 --- a/packages/react-server/src/ReactFizzServer.js +++ b/packages/react-server/src/ReactFizzServer.js @@ -1732,12 +1732,12 @@ function renderSuspenseListRows( const prevRow = task.row; const totalChildren = rows.length; + let previousSuspenseListRow: null | SuspenseListRow = null; if (task.replay !== null) { // Replay // First we need to check if we have any resume slots at this level. const resumeSlots = task.replay.slots; if (resumeSlots !== null && typeof resumeSlots === 'object') { - let previousSuspenseListRow: null | SuspenseListRow = null; for (let n = 0; n < totalChildren; n++) { // Since we are going to resume into a slot whose order was already // determined by the prerender, we can safely resume it even in reverse @@ -1763,7 +1763,6 @@ function renderSuspenseListRows( } } } else { - let previousSuspenseListRow: null | SuspenseListRow = null; for (let n = 0; n < totalChildren; n++) { // Since we are going to resume into a slot whose order was already // determined by the prerender, we can safely resume it even in reverse @@ -1787,7 +1786,6 @@ function renderSuspenseListRows( task = ((task: any): RenderTask); // Refined if (revealOrder !== 'backwards') { // Forwards direction - let previousSuspenseListRow: null | SuspenseListRow = null; for (let i = 0; i < totalChildren; i++) { const node = rows[i]; if (__DEV__) { @@ -1809,7 +1807,6 @@ function renderSuspenseListRows( const parentSegment = task.blockedSegment; const childIndex = parentSegment.children.length; const insertionIndex = parentSegment.chunks.length; - let previousSuspenseListRow: null | SuspenseListRow = null; for (let i = totalChildren - 1; i >= 0; i--) { const node = rows[i]; task.row = previousSuspenseListRow = createSuspenseListRow( @@ -1859,6 +1856,17 @@ function renderSuspenseListRows( } } + if ( + prevRow !== null && + previousSuspenseListRow !== null && + previousSuspenseListRow.pendingTasks > 0 + ) { + // If we are part of an outer SuspenseList and our last row is still pending, then that blocks + // the parent row from completing. We can continue the chain. + prevRow.pendingTasks++; + previousSuspenseListRow.next = prevRow; + } + // Because this context is always set right before rendering every child, we // only need to reset it to the previous value at the very end. task.treeContext = prevTreeContext;