Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't cut off the tail of a SuspenseList if hydrating #18854

Merged
merged 1 commit into from May 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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