Skip to content

Commit

Permalink
Use hydration lanes when scheduling hydration work
Browse files Browse the repository at this point in the history
When scheduling the initial root and when using unstable_scheduleHydration.
  • Loading branch information
sebmarkbage committed Dec 13, 2024
1 parent 62d07c1 commit 8282a92
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,39 @@ describe('ReactDOMFizzShellHydration', () => {
},
);

// @gate enableHydrationLaneScheduling
it(
'updating the root at same priority as initial hydration does not ' +
'force a client render',
async () => {
function App() {
return <Text text="Initial" />;
}

// Server render
await resolveText('Initial');
await serverAct(async () => {
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
pipe(writable);
});
assertLog(['Initial']);

await clientAct(async () => {
let root;
startTransition(() => {
root = ReactDOMClient.hydrateRoot(container, <App />);
});
// This has lower priority than the initial hydration, so the update
// won't be processed until after hydration finishes.
startTransition(() => {
root.render(<Text text="Updated" />);
});
});
assertLog(['Initial', 'Updated']);
expect(container.textContent).toBe('Updated');
},
);

it('updating the root while the shell is suspended forces a client render', async () => {
function App() {
return <AsyncText text="Shell" />;
Expand Down
101 changes: 53 additions & 48 deletions packages/react-reconciler/src/ReactFiberLane.js
Original file line number Diff line number Diff line change
Expand Up @@ -995,61 +995,66 @@ export function getBumpedLaneForHydration(
renderLanes: Lanes,
): Lane {
const renderLane = getHighestPriorityLane(renderLanes);

let lane;
if ((renderLane & SyncUpdateLanes) !== NoLane) {
lane = SyncHydrationLane;
} else {
switch (renderLane) {
case SyncLane:
lane = SyncHydrationLane;
break;
case InputContinuousLane:
lane = InputContinuousHydrationLane;
break;
case DefaultLane:
lane = DefaultHydrationLane;
break;
case TransitionLane1:
case TransitionLane2:
case TransitionLane3:
case TransitionLane4:
case TransitionLane5:
case TransitionLane6:
case TransitionLane7:
case TransitionLane8:
case TransitionLane9:
case TransitionLane10:
case TransitionLane11:
case TransitionLane12:
case TransitionLane13:
case TransitionLane14:
case TransitionLane15:
case RetryLane1:
case RetryLane2:
case RetryLane3:
case RetryLane4:
lane = TransitionHydrationLane;
break;
case IdleLane:
lane = IdleHydrationLane;
break;
default:
// Everything else is already either a hydration lane, or shouldn't
// be retried at a hydration lane.
lane = NoLane;
break;
}
}

const bumpedLane =
(renderLane & SyncUpdateLanes) !== NoLane
? // Unify sync lanes. We don't do this inside getBumpedLaneForHydrationByLane
// because that causes things to flush synchronously when they shouldn't.
// TODO: This is not coherent but that's beacuse the unification is not coherent.
// We need to get merge these into an actual single lane.
SyncHydrationLane
: getBumpedLaneForHydrationByLane(renderLane);
// Check if the lane we chose is suspended. If so, that indicates that we
// already attempted and failed to hydrate at that level. Also check if we're
// already rendering that lane, which is rare but could happen.
if ((lane & (root.suspendedLanes | renderLanes)) !== NoLane) {
// TODO: This should move into the caller to decide whether giving up is valid.
if ((bumpedLane & (root.suspendedLanes | renderLanes)) !== NoLane) {
// Give up trying to hydrate and fall back to client render.
return NoLane;
}
return bumpedLane;
}

export function getBumpedLaneForHydrationByLane(lane: Lane): Lane {
switch (lane) {
case SyncLane:
lane = SyncHydrationLane;
break;
case InputContinuousLane:
lane = InputContinuousHydrationLane;
break;
case DefaultLane:
lane = DefaultHydrationLane;
break;
case TransitionLane1:
case TransitionLane2:
case TransitionLane3:
case TransitionLane4:
case TransitionLane5:
case TransitionLane6:
case TransitionLane7:
case TransitionLane8:
case TransitionLane9:
case TransitionLane10:
case TransitionLane11:
case TransitionLane12:
case TransitionLane13:
case TransitionLane14:
case TransitionLane15:
case RetryLane1:
case RetryLane2:
case RetryLane3:
case RetryLane4:
lane = TransitionHydrationLane;
break;
case IdleLane:
lane = IdleHydrationLane;
break;
default:
// Everything else is already either a hydration lane, or shouldn't
// be retried at a hydration lane.
lane = NoLane;
break;
}
return lane;
}

Expand Down
16 changes: 13 additions & 3 deletions packages/react-reconciler/src/ReactFiberReconciler.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ import {
} from './ReactWorkTags';
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
import isArray from 'shared/isArray';
import {enableSchedulingProfiler} from 'shared/ReactFeatureFlags';
import {
enableSchedulingProfiler,
enableHydrationLaneScheduling,
} from 'shared/ReactFeatureFlags';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import {
getPublicInstance,
Expand Down Expand Up @@ -91,6 +94,7 @@ import {
SelectiveHydrationLane,
getHighestPriorityPendingLanes,
higherPriorityLane,
getBumpedLaneForHydrationByLane,
} from './ReactFiberLane';
import {
scheduleRefresh,
Expand Down Expand Up @@ -322,7 +326,10 @@ export function createHydrationContainer(
// the update to schedule work on the root fiber (and, for legacy roots, to
// enqueue the callback if one is provided).
const current = root.current;
const lane = requestUpdateLane(current);
let lane = requestUpdateLane(current);
if (enableHydrationLaneScheduling) {
lane = getBumpedLaneForHydrationByLane(lane);
}
const update = createUpdate(lane);
update.callback =
callback !== undefined && callback !== null ? callback : null;
Expand Down Expand Up @@ -533,7 +540,10 @@ export function attemptHydrationAtCurrentPriority(fiber: Fiber): void {
// their priority other than synchronously flush it.
return;
}
const lane = requestUpdateLane(fiber);
let lane = requestUpdateLane(fiber);
if (enableHydrationLaneScheduling) {
lane = getBumpedLaneForHydrationByLane(lane);
}
const root = enqueueConcurrentRenderForLane(fiber, lane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, lane);
Expand Down
2 changes: 1 addition & 1 deletion packages/shared/ReactFeatureFlags.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export const enableSuspenseAvoidThisFallbackFizz = false;

export const enableCPUSuspense = __EXPERIMENTAL__;

export const enableHydrationLaneScheduling = __EXPERIMENTAL__;
export const enableHydrationLaneScheduling = true;

// Enables useMemoCache hook, intended as a compilation target for
// auto-memoization.
Expand Down

0 comments on commit 8282a92

Please sign in to comment.