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

useDeferredValue should skip initialValue if it suspends #27509

Merged
merged 2 commits into from
Oct 17, 2023
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

24 changes: 12 additions & 12 deletions packages/react-devtools-shared/src/__tests__/preprocessData-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2001,15 +2001,15 @@ describe('Timeline profiler', () => {
524288 => "Transition",
1048576 => "Transition",
2097152 => "Transition",
4194304 => "Transition",
4194304 => "Retry",
8388608 => "Retry",
16777216 => "Retry",
33554432 => "Retry",
67108864 => "Retry",
134217728 => "SelectiveHydration",
268435456 => "IdleHydration",
536870912 => "Idle",
1073741824 => "Offscreen",
67108864 => "SelectiveHydration",
134217728 => "IdleHydration",
268435456 => "Idle",
536870912 => "Offscreen",
1073741824 => "Deferred",
},
"laneToReactMeasureMap": Map {
1 => [],
Expand Down Expand Up @@ -2269,15 +2269,15 @@ describe('Timeline profiler', () => {
524288 => "Transition",
1048576 => "Transition",
2097152 => "Transition",
4194304 => "Transition",
4194304 => "Retry",
8388608 => "Retry",
16777216 => "Retry",
33554432 => "Retry",
67108864 => "Retry",
134217728 => "SelectiveHydration",
268435456 => "IdleHydration",
536870912 => "Idle",
1073741824 => "Offscreen",
67108864 => "SelectiveHydration",
134217728 => "IdleHydration",
268435456 => "Idle",
536870912 => "Offscreen",
1073741824 => "Deferred",
},
"laneToReactMeasureMap": Map {
1 => [],
Expand Down
23 changes: 15 additions & 8 deletions packages/react-reconciler/src/ReactFiberHooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,16 +61,17 @@ import {
NoLane,
SyncLane,
OffscreenLane,
DeferredLane,
NoLanes,
isSubsetOfLanes,
includesBlockingLane,
includesOnlyNonUrgentLanes,
claimNextTransitionLane,
mergeLanes,
removeLanes,
intersectLanes,
isTransitionLane,
markRootEntangled,
includesSomeLane,
} from './ReactFiberLane';
import {
ContinuousEventPriority,
Expand Down Expand Up @@ -101,6 +102,7 @@ import {
getWorkInProgressRootRenderLanes,
scheduleUpdateOnFiber,
requestUpdateLane,
requestDeferredLane,
markSkippedUpdateLanes,
isInvalidExecutionContextForEventFunction,
} from './ReactFiberWorkLoop';
Expand Down Expand Up @@ -2665,16 +2667,21 @@ function rerenderDeferredValue<T>(value: T, initialValue?: T): T {
}

function mountDeferredValueImpl<T>(hook: Hook, value: T, initialValue?: T): T {
if (enableUseDeferredValueInitialArg && initialValue !== undefined) {
if (
enableUseDeferredValueInitialArg &&
// When `initialValue` is provided, we defer the initial render even if the
// current render is not synchronous.
// TODO: However, to avoid waterfalls, we should not defer if this render
// was itself spawned by an earlier useDeferredValue. Plan is to add a
// Deferred lane to track this.
initialValue !== undefined &&
// However, to avoid waterfalls, we do not defer if this render
// was itself spawned by an earlier useDeferredValue. Check if DeferredLane
// is part of the render lanes.
!includesSomeLane(renderLanes, DeferredLane)
) {
// Render with the initial value
hook.memoizedState = initialValue;

// Schedule a deferred render
const deferredLane = claimNextTransitionLane();
// Schedule a deferred render to switch to the final value.
const deferredLane = requestDeferredLane();
currentlyRenderingFiber.lanes = mergeLanes(
currentlyRenderingFiber.lanes,
deferredLane,
Expand Down Expand Up @@ -2710,7 +2717,7 @@ function updateDeferredValueImpl<T>(

if (!is(value, prevValue)) {
// Schedule a deferred render
const deferredLane = claimNextTransitionLane();
const deferredLane = requestDeferredLane();
currentlyRenderingFiber.lanes = mergeLanes(
currentlyRenderingFiber.lanes,
deferredLane,
Expand Down
82 changes: 65 additions & 17 deletions packages/react-reconciler/src/ReactFiberLane.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export const SyncUpdateLanes: Lane = enableUnifiedSyncLane
: SyncLane;

const TransitionHydrationLane: Lane = /* */ 0b0000000000000000000000001000000;
const TransitionLanes: Lanes = /* */ 0b0000000011111111111111110000000;
const TransitionLanes: Lanes = /* */ 0b0000000001111111111111110000000;
const TransitionLane1: Lane = /* */ 0b0000000000000000000000010000000;
const TransitionLane2: Lane = /* */ 0b0000000000000000000000100000000;
const TransitionLane3: Lane = /* */ 0b0000000000000000000001000000000;
Expand All @@ -68,24 +68,24 @@ const TransitionLane12: Lane = /* */ 0b0000000000001000000
const TransitionLane13: Lane = /* */ 0b0000000000010000000000000000000;
const TransitionLane14: Lane = /* */ 0b0000000000100000000000000000000;
const TransitionLane15: Lane = /* */ 0b0000000001000000000000000000000;
const TransitionLane16: Lane = /* */ 0b0000000010000000000000000000000;

const RetryLanes: Lanes = /* */ 0b0000111100000000000000000000000;
const RetryLane1: Lane = /* */ 0b0000000100000000000000000000000;
const RetryLane2: Lane = /* */ 0b0000001000000000000000000000000;
const RetryLane3: Lane = /* */ 0b0000010000000000000000000000000;
const RetryLane4: Lane = /* */ 0b0000100000000000000000000000000;
const RetryLanes: Lanes = /* */ 0b0000011110000000000000000000000;
const RetryLane1: Lane = /* */ 0b0000000010000000000000000000000;
const RetryLane2: Lane = /* */ 0b0000000100000000000000000000000;
const RetryLane3: Lane = /* */ 0b0000001000000000000000000000000;
const RetryLane4: Lane = /* */ 0b0000010000000000000000000000000;

export const SomeRetryLane: Lane = RetryLane1;

export const SelectiveHydrationLane: Lane = /* */ 0b0001000000000000000000000000000;
export const SelectiveHydrationLane: Lane = /* */ 0b0000100000000000000000000000000;

const NonIdleLanes: Lanes = /* */ 0b0001111111111111111111111111111;
const NonIdleLanes: Lanes = /* */ 0b0000111111111111111111111111111;

export const IdleHydrationLane: Lane = /* */ 0b0010000000000000000000000000000;
export const IdleLane: Lane = /* */ 0b0100000000000000000000000000000;
export const IdleHydrationLane: Lane = /* */ 0b0001000000000000000000000000000;
export const IdleLane: Lane = /* */ 0b0010000000000000000000000000000;

export const OffscreenLane: Lane = /* */ 0b1000000000000000000000000000000;
export const OffscreenLane: Lane = /* */ 0b0100000000000000000000000000000;
export const DeferredLane: Lane = /* */ 0b1000000000000000000000000000000;

// Any lane that might schedule an update. This is used to detect infinite
// update loops, so it doesn't include hydration lanes or retries.
Expand Down Expand Up @@ -135,6 +135,9 @@ export function getLabelForLane(lane: Lane): string | void {
if (lane & OffscreenLane) {
return 'Offscreen';
}
if (lane & DeferredLane) {
return 'Deferred';
}
}
}

Expand Down Expand Up @@ -180,7 +183,6 @@ function getHighestPriorityLanes(lanes: Lanes | Lane): Lanes {
case TransitionLane13:
case TransitionLane14:
case TransitionLane15:
case TransitionLane16:
return lanes & TransitionLanes;
case RetryLane1:
case RetryLane2:
Expand All @@ -195,6 +197,10 @@ function getHighestPriorityLanes(lanes: Lanes | Lane): Lanes {
return IdleLane;
case OffscreenLane:
return OffscreenLane;
case DeferredLane:
// This shouldn't be reachable because deferred work is always entangled
// with something else.
return NoLanes;
default:
if (__DEV__) {
console.error(
Expand Down Expand Up @@ -367,7 +373,6 @@ function computeExpirationTime(lane: Lane, currentTime: number) {
case TransitionLane13:
case TransitionLane14:
case TransitionLane15:
case TransitionLane16:
return currentTime + 5000;
case RetryLane1:
case RetryLane2:
Expand All @@ -383,6 +388,7 @@ function computeExpirationTime(lane: Lane, currentTime: number) {
case IdleHydrationLane:
case IdleLane:
case OffscreenLane:
case DeferredLane:
// Anything idle priority or lower should never expire.
return NoTimestamp;
default:
Expand Down Expand Up @@ -616,7 +622,11 @@ export function markRootUpdated(root: FiberRoot, updateLane: Lane) {
}
}

export function markRootSuspended(root: FiberRoot, suspendedLanes: Lanes) {
export function markRootSuspended(
root: FiberRoot,
suspendedLanes: Lanes,
spawnedLane: Lane,
) {
root.suspendedLanes |= suspendedLanes;
root.pingedLanes &= ~suspendedLanes;

Expand All @@ -631,13 +641,21 @@ export function markRootSuspended(root: FiberRoot, suspendedLanes: Lanes) {

lanes &= ~lane;
}

if (spawnedLane !== NoLane) {
markSpawnedDeferredLane(root, spawnedLane, suspendedLanes);
}
}

export function markRootPinged(root: FiberRoot, pingedLanes: Lanes) {
root.pingedLanes |= root.suspendedLanes & pingedLanes;
}

export function markRootFinished(root: FiberRoot, remainingLanes: Lanes) {
export function markRootFinished(
root: FiberRoot,
remainingLanes: Lanes,
spawnedLane: Lane,
) {
const noLongerPendingLanes = root.pendingLanes & ~remainingLanes;

root.pendingLanes = remainingLanes;
Expand Down Expand Up @@ -683,6 +701,37 @@ export function markRootFinished(root: FiberRoot, remainingLanes: Lanes) {

lanes &= ~lane;
}

if (spawnedLane !== NoLane) {
markSpawnedDeferredLane(
root,
spawnedLane,
// This render finished successfully without suspending, so we don't need
// to entangle the spawned task with the parent task.
NoLanes,
);
}
}

function markSpawnedDeferredLane(
root: FiberRoot,
spawnedLane: Lane,
entangledLanes: Lanes,
) {
// This render spawned a deferred task. Mark it as pending.
root.pendingLanes |= spawnedLane;
root.suspendedLanes &= ~spawnedLane;

// Entangle the spawned lane with the DeferredLane bit so that we know it
// was the result of another render. This lets us avoid a useDeferredValue
// waterfall — only the first level will defer.
const spawnedLaneIndex = laneToIndex(spawnedLane);
root.entangledLanes |= spawnedLane;
root.entanglements[spawnedLaneIndex] |=
DeferredLane |
// If the parent render task suspended, we must also entangle those lanes
// with the spawned task.
entangledLanes;
}

export function markRootEntangled(root: FiberRoot, entangledLanes: Lanes) {
Expand Down Expand Up @@ -795,7 +844,6 @@ export function getBumpedLaneForHydration(
case TransitionLane13:
case TransitionLane14:
case TransitionLane15:
case TransitionLane16:
case RetryLane1:
case RetryLane2:
case RetryLane3:
Expand Down
Loading