From 18de3b6e7c8a93289d9df67339d876bba6ff3e0c Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Tue, 26 May 2020 18:53:50 -0700 Subject: [PATCH] Bug: Spawning hydration in response to Idle update (#19011) * Bug: Spawning hydration in response to Idle update Adds a test that fails in the new fork. * Fix typos related to Idle priority These are just silly mistakes that weren't caught by any of our tests. There's a lot of duplication in the Lanes module right now. It's also not super stable as we continue to refine our heuristics. Hopefully the final state is simpler and less prone to these types of mistakes. --- ...DOMServerPartialHydration-test.internal.js | 74 +++++++++++++++++++ .../react-reconciler/src/ReactFiberLane.js | 3 +- 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js index 6080a26964..419dd5a587 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js @@ -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 ( +
+ + + + + +
+ ); + } + + suspend = false; + const finalHTML = ReactDOMServer.renderToString( + , + ); + 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(); + 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(); + }); + + // 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; diff --git a/packages/react-reconciler/src/ReactFiberLane.js b/packages/react-reconciler/src/ReactFiberLane.js index d107913e10..72a6b015f3 100644 --- a/packages/react-reconciler/src/ReactFiberLane.js +++ b/packages/react-reconciler/src/ReactFiberLane.js @@ -208,6 +208,7 @@ function getHighestPriorityLanes(lanes: Lanes | Lane): Lanes { return_updateRangeEnd = IdleUpdateRangeStart; return IdleHydrationLane; } else { + return_highestLanePriority = IdleLanePriority; return_updateRangeEnd = IdleUpdateRangeEnd; return idleLanes; } @@ -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; }