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);
+ });
});