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

Bug: Spawning hydration in response to Idle update #19011

Merged
merged 2 commits into from May 27, 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 @@ -587,6 +587,80 @@ describe('ReactDOMServerPartialHydration', () => {
expect(span.className).toBe('hi');
});

// @gate experimental
it('blocks updates to hydrate the content first if props changed at idle priority', async () => {
let suspend = false;
let resolve;
const promise = new Promise(resolvePromise => (resolve = resolvePromise));
const ref = React.createRef();

function Child({text}) {
if (suspend) {
throw promise;
} else {
return text;
}
}

function App({text, className}) {
return (
<div>
<Suspense fallback="Loading...">
<span ref={ref} className={className}>
<Child text={text} />
</span>
</Suspense>
</div>
);
}

suspend = false;
const finalHTML = ReactDOMServer.renderToString(
<App text="Hello" className="hello" />,
);
const container = document.createElement('div');
container.innerHTML = finalHTML;

const span = container.getElementsByTagName('span')[0];

// On the client we don't have all data yet but we want to start
// hydrating anyway.
suspend = true;
const root = ReactDOM.createRoot(container, {hydrate: true});
root.render(<App text="Hello" className="hello" />);
Scheduler.unstable_flushAll();
jest.runAllTimers();

expect(ref.current).toBe(null);
expect(span.textContent).toBe('Hello');

// Schedule an update at idle priority
Scheduler.unstable_runWithPriority(Scheduler.unstable_IdlePriority, () => {
root.render(<App text="Hi" className="hi" />);
});

// At the same time, resolving the promise so that rendering can complete.
suspend = false;
resolve();
await promise;

// This should first complete the hydration and then flush the update onto the hydrated state.
Scheduler.unstable_flushAll();
jest.runAllTimers();

// The new span should be the same since we should have successfully hydrated
// before changing it.
const newSpan = container.getElementsByTagName('span')[0];
expect(span).toBe(newSpan);

// We should now have fully rendered with a ref on the new span.
expect(ref.current).toBe(span);
expect(span.textContent).toBe('Hi');
// If we ended up hydrating the existing content, we won't have properly
// patched up the tree, which might mean we haven't patched the className.
expect(span.className).toBe('hi');
});

// @gate experimental
it('shows the fallback if props have changed before hydration completes and is still suspended', async () => {
let suspend = false;
Expand Down
3 changes: 2 additions & 1 deletion packages/react-reconciler/src/ReactFiberLane.js
Expand Up @@ -208,6 +208,7 @@ function getHighestPriorityLanes(lanes: Lanes | Lane): Lanes {
return_updateRangeEnd = IdleUpdateRangeStart;
return IdleHydrationLane;
} else {
return_highestLanePriority = IdleLanePriority;
return_updateRangeEnd = IdleUpdateRangeEnd;
return idleLanes;
}
Expand Down Expand Up @@ -527,7 +528,7 @@ export function findUpdateLane(
// Should be handled by findTransitionLane instead
break;
case IdleLanePriority:
let lane = findLane(IdleUpdateRangeStart, IdleUpdateRangeEnd, IdleLanes);
let lane = findLane(IdleUpdateRangeStart, IdleUpdateRangeEnd, wipLanes);
if (lane === NoLane) {
lane = IdleHydrationLane;
}
Expand Down