Skip to content

Commit

Permalink
Don't cut off the tail of a SuspenseList if hydrating (#18854)
Browse files Browse the repository at this point in the history
  • Loading branch information
sebmarkbage committed May 7, 2020
1 parent 55f5cde commit edf6eac
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 0 deletions.
Expand Up @@ -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 (
<>
<Before />
<Suspense fallback="Loading A">
<span>A</span>
</Suspense>
<After />
</>
);
}

function App() {
return (
<Suspense fallback={null}>
<SuspenseList revealOrder="forwards" tail="hidden">
<FirstRow />
<Suspense fallback="Loading B">
<Child>
<span ref={ref}>B</span>
</Child>
</Suspense>
</SuspenseList>
</Suspense>
);
}

suspend = false;
const html = ReactDOMServer.renderToString(<App />);
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(<App />);
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;
Expand Down
6 changes: 6 additions & 0 deletions packages/react-reconciler/src/ReactFiberCompleteWork.new.js
Expand Up @@ -118,6 +118,7 @@ import {
prepareToHydrateHostSuspenseInstance,
popHydrationState,
resetHydrationState,
getIsHydrating,
} from './ReactFiberHydrationContext.new';
import {
enableSchedulerTracing,
Expand Down Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions packages/react-reconciler/src/ReactFiberCompleteWork.old.js
Expand Up @@ -115,6 +115,7 @@ import {
prepareToHydrateHostSuspenseInstance,
popHydrationState,
resetHydrationState,
getIsHydrating,
} from './ReactFiberHydrationContext.old';
import {
enableSchedulerTracing,
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit edf6eac

Please sign in to comment.