diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.js b/packages/react-reconciler/src/ReactFiberCompleteWork.js index b599388864c55..a8ea88728e21f 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.js @@ -1098,10 +1098,11 @@ function completeWork( // This might have been modified. if ( renderState.tail === null && - renderState.tailMode === 'hidden' + renderState.tailMode === 'hidden' && + !renderedTail.alternate ) { // We need to delete the row we just rendered. - // Reset the effect list to what it w as before we rendered this + // Reset the effect list to what it was before we rendered this // child. The nested children have already appended themselves. let lastEffect = (workInProgress.lastEffect = renderState.lastEffect); diff --git a/packages/react-reconciler/src/__tests__/ReactSuspenseList-test.internal.js b/packages/react-reconciler/src/__tests__/ReactSuspenseList-test.internal.js index c2c9b333abaaf..506737b7f4f6f 100644 --- a/packages/react-reconciler/src/__tests__/ReactSuspenseList-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactSuspenseList-test.internal.js @@ -2023,7 +2023,7 @@ describe('ReactSuspenseList', () => { ); }); - it('eventually resolves two nested forwards suspense list with a hidden tail', async () => { + it('eventually resolves two nested forwards suspense lists with a hidden tail', async () => { let B = createAsyncText('B'); function Foo({showB}) { @@ -2135,4 +2135,92 @@ describe('ReactSuspenseList', () => { , ); }); + + it('is able to re-suspend the last rows during an update with hidden', async () => { + let AsyncB = createAsyncText('B'); + + let setAsyncB; + + function B() { + let [shouldBeAsync, setAsync] = React.useState(false); + setAsyncB = setAsync; + + return shouldBeAsync ? ( + }> + + + ) : ( + + ); + } + + function Foo({updateList}) { + return ( + + }> + + + + + ); + } + + ReactNoop.render(); + + expect(Scheduler).toFlushAndYield(['A', 'Sync B']); + + expect(ReactNoop).toMatchRenderedOutput( + <> + A + Sync B + , + ); + + let previousInst = setAsyncB; + + // During an update we suspend on B. + ReactNoop.act(() => setAsyncB(true)); + + expect(Scheduler).toHaveYielded([ + 'Suspend! [B]', + 'Loading B', + // The second pass is the "force hide" pass + 'Loading B', + ]); + + expect(ReactNoop).toMatchRenderedOutput( + <> + A + Loading B + , + ); + + // Before we resolve we'll rerender the whole list. + // This should leave the tree intact. + ReactNoop.act(() => ReactNoop.render()); + + expect(Scheduler).toHaveYielded(['A', 'Suspend! [B]', 'Loading B']); + + expect(ReactNoop).toMatchRenderedOutput( + <> + A + Loading B + , + ); + + await AsyncB.resolve(); + + expect(Scheduler).toFlushAndYield(['B']); + + expect(ReactNoop).toMatchRenderedOutput( + <> + A + B + , + ); + + // This should be the same instance. I.e. it didn't + // remount. + expect(previousInst).toBe(setAsyncB); + }); });