diff --git a/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js
index a55b8774c0ac..dd1c7548c403 100644
--- a/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js
+++ b/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js
@@ -1698,6 +1698,99 @@ describe('ReactDOMServerPartialHydration', () => {
expect(container.textContent).toBe('ABC');
});
+ // @gate experimental
+ it('clears server boundaries when SuspenseList does a second pass', async () => {
+ let suspend = false;
+ let resolve;
+ const promise = new Promise(resolvePromise => (resolve = resolvePromise));
+
+ const ref = React.createRef();
+
+ function Child({children}) {
+ if (suspend) {
+ throw promise;
+ } else {
+ return children;
+ }
+ }
+
+ function Before() {
+ Scheduler.unstable_yieldValue('Before');
+ return null;
+ }
+
+ function After() {
+ Scheduler.unstable_yieldValue('After');
+ return null;
+ }
+
+ function FirstRow() {
+ return (
+ <>
+
+
+ A
+
+
+ >
+ );
+ }
+
+ function App() {
+ return (
+
+
+
+
+
+ B
+
+
+
+
+ );
+ }
+
+ suspend = false;
+ const html = ReactDOMServer.renderToString();
+ expect(Scheduler).toHaveYielded(['Before', 'After']);
+
+ const container = document.createElement('div');
+ container.innerHTML = html;
+
+ const b = container.getElementsByTagName('span')[1];
+ expect(b.textContent).toBe('B');
+
+ const root = ReactDOM.createRoot(container, {hydrate: true});
+
+ // Increase hydration priority to higher than "offscreen".
+ ReactDOM.unstable_scheduleHydration(b);
+
+ suspend = true;
+
+ await act(async () => {
+ root.render();
+ expect(Scheduler).toFlushAndYieldThrough(['Before']);
+ // This took a long time to render.
+ Scheduler.unstable_advanceTime(1000);
+ expect(Scheduler).toFlushAndYield(['After']);
+ // This will cause us to skip the second row completely.
+ });
+
+ // We haven't hydrated the second child but the placeholder is still in the list.
+ expect(ref.current).toBe(null);
+ expect(container.textContent).toBe('AB');
+
+ suspend = false;
+ await act(async () => {
+ // Resolve the boundary to be in its resolved final state.
+ await resolve();
+ });
+
+ expect(container.textContent).toBe('AB');
+ expect(ref.current).toBe(b);
+ });
+
// @gate experimental
it('can client render nested boundaries', async () => {
let suspend = false;
diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.new.js b/packages/react-reconciler/src/ReactFiberCompleteWork.new.js
index e420bb6916c7..577224097c24 100644
--- a/packages/react-reconciler/src/ReactFiberCompleteWork.new.js
+++ b/packages/react-reconciler/src/ReactFiberCompleteWork.new.js
@@ -118,6 +118,7 @@ import {
prepareToHydrateHostSuspenseInstance,
popHydrationState,
resetHydrationState,
+ getIsHydrating,
} from './ReactFiberHydrationContext.new';
import {
enableSchedulerTracing,
@@ -579,6 +580,11 @@ function cutOffTailIfNeeded(
renderState: SuspenseListRenderState,
hasRenderedATailFallback: boolean,
) {
+ if (getIsHydrating()) {
+ // If we're hydrating, we should consume as many items as we can
+ // so we don't leave any behind.
+ return;
+ }
switch (renderState.tailMode) {
case 'hidden': {
// Any insertions at the end of the tail list after this point
diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.old.js b/packages/react-reconciler/src/ReactFiberCompleteWork.old.js
index d024c9711ce1..cc7ed6017ed7 100644
--- a/packages/react-reconciler/src/ReactFiberCompleteWork.old.js
+++ b/packages/react-reconciler/src/ReactFiberCompleteWork.old.js
@@ -115,6 +115,7 @@ import {
prepareToHydrateHostSuspenseInstance,
popHydrationState,
resetHydrationState,
+ getIsHydrating,
} from './ReactFiberHydrationContext.old';
import {
enableSchedulerTracing,
@@ -575,6 +576,11 @@ function cutOffTailIfNeeded(
renderState: SuspenseListRenderState,
hasRenderedATailFallback: boolean,
) {
+ if (getIsHydrating()) {
+ // If we're hydrating, we should consume as many items as we can
+ // so we don't leave any behind.
+ return;
+ }
switch (renderState.tailMode) {
case 'hidden': {
// Any insertions at the end of the tail list after this point