@@ -366,6 +366,8 @@ let workInProgressRootInterleavedUpdatedLanes: Lanes = NoLanes;
366366let workInProgressRootRenderPhaseUpdatedLanes : Lanes = NoLanes ;
367367// Lanes that were pinged (in an interleaved event) during this render.
368368let workInProgressRootPingedLanes : Lanes = NoLanes ;
369+ // If this lane scheduled deferred work, this is the lane of the deferred task.
370+ let workInProgressDeferredLane : Lane = NoLane ;
369371// Errors that are thrown during the render phase.
370372let workInProgressRootConcurrentErrors : Array < CapturedValue < mixed >> | null =
371373 null ;
@@ -683,6 +685,27 @@ function requestRetryLane(fiber: Fiber) {
683685 return claimNextRetryLane ( ) ;
684686}
685687
688+ export function requestDeferredLane ( ) : Lane {
689+ if ( workInProgressDeferredLane === NoLane ) {
690+ // If there are multiple useDeferredValue hooks in the same render, the
691+ // tasks that they spawn should all be batched together, so they should all
692+ // receive the same lane.
693+ if ( includesOnlyRetries ( workInProgressRootRenderLanes ) ) {
694+ // Retries are slightly lower priority than transitions, so if Retry task
695+ // spawns a deferred task, the deferred task is also considered a Retry.
696+ workInProgressDeferredLane = claimNextRetryLane ( ) ;
697+ } else if ( includesSomeLane ( workInProgressRootRenderLanes , OffscreenLane ) ) {
698+ // There's only one OffscreenLane, so if it contains deferred work, we
699+ // should just reschedule using the same lane.
700+ workInProgressDeferredLane = OffscreenLane ;
701+ } else {
702+ // Everything else is spawned as a transition.
703+ workInProgressDeferredLane = requestTransitionLane ( ) ;
704+ }
705+ }
706+ return workInProgressDeferredLane ;
707+ }
708+
686709export function scheduleUpdateOnFiber (
687710 root : FiberRoot ,
688711 fiber : Fiber ,
@@ -712,7 +735,11 @@ export function scheduleUpdateOnFiber(
712735 // The incoming update might unblock the current render. Interrupt the
713736 // current attempt and restart from the top.
714737 prepareFreshStack ( root , NoLanes ) ;
715- markRootSuspended ( root , workInProgressRootRenderLanes ) ;
738+ markRootSuspended (
739+ root ,
740+ workInProgressRootRenderLanes ,
741+ workInProgressDeferredLane ,
742+ ) ;
716743 }
717744
718745 // Mark that the root has a pending update.
@@ -792,7 +819,11 @@ export function scheduleUpdateOnFiber(
792819 // effect of interrupting the current render and switching to the update.
793820 // TODO: Make sure this doesn't override pings that happen while we've
794821 // already started rendering.
795- markRootSuspended ( root , workInProgressRootRenderLanes ) ;
822+ markRootSuspended (
823+ root ,
824+ workInProgressRootRenderLanes ,
825+ workInProgressDeferredLane ,
826+ ) ;
796827 }
797828 }
798829
@@ -903,7 +934,7 @@ export function performConcurrentWorkOnRoot(
903934 // The render unwound without completing the tree. This happens in special
904935 // cases where need to exit the current render without producing a
905936 // consistent tree or committing.
906- markRootSuspended ( root , lanes ) ;
937+ markRootSuspended ( root , lanes , NoLane ) ;
907938 } else {
908939 // The render completed.
909940
@@ -947,7 +978,7 @@ export function performConcurrentWorkOnRoot(
947978 if ( exitStatus === RootFatalErrored ) {
948979 const fatalError = workInProgressRootFatalError ;
949980 prepareFreshStack ( root , NoLanes ) ;
950- markRootSuspended ( root , lanes ) ;
981+ markRootSuspended ( root , lanes , NoLane ) ;
951982 ensureRootIsScheduled ( root ) ;
952983 throw fatalError ;
953984 }
@@ -1074,7 +1105,7 @@ function finishConcurrentRender(
10741105 // This is a transition, so we should exit without committing a
10751106 // placeholder and without scheduling a timeout. Delay indefinitely
10761107 // until we receive more data.
1077- markRootSuspended ( root , lanes ) ;
1108+ markRootSuspended ( root , lanes , workInProgressDeferredLane ) ;
10781109 return ;
10791110 }
10801111 // Commit the placeholder.
@@ -1096,6 +1127,7 @@ function finishConcurrentRender(
10961127 root ,
10971128 workInProgressRootRecoverableErrors ,
10981129 workInProgressTransitions ,
1130+ workInProgressDeferredLane ,
10991131 ) ;
11001132 } else {
11011133 if (
@@ -1109,7 +1141,7 @@ function finishConcurrentRender(
11091141
11101142 // Don't bother with a very short suspense time.
11111143 if ( msUntilTimeout > 10 ) {
1112- markRootSuspended ( root , lanes ) ;
1144+ markRootSuspended ( root , lanes , workInProgressDeferredLane ) ;
11131145
11141146 const nextLanes = getNextLanes ( root , NoLanes ) ;
11151147 if ( nextLanes !== NoLanes ) {
@@ -1131,6 +1163,7 @@ function finishConcurrentRender(
11311163 workInProgressRootRecoverableErrors ,
11321164 workInProgressTransitions ,
11331165 lanes ,
1166+ workInProgressDeferredLane ,
11341167 ) ,
11351168 msUntilTimeout ,
11361169 ) ;
@@ -1143,6 +1176,7 @@ function finishConcurrentRender(
11431176 workInProgressRootRecoverableErrors ,
11441177 workInProgressTransitions ,
11451178 lanes ,
1179+ workInProgressDeferredLane ,
11461180 ) ;
11471181 }
11481182}
@@ -1153,6 +1187,7 @@ function commitRootWhenReady(
11531187 recoverableErrors : Array < CapturedValue < mixed >> | null ,
11541188 transitions : Array < Transition > | null ,
11551189 lanes : Lanes ,
1190+ spawnedLane : Lane ,
11561191) {
11571192 // TODO: Combine retry throttling with Suspensey commits. Right now they run
11581193 // one after the other.
@@ -1180,13 +1215,13 @@ function commitRootWhenReady(
11801215 root . cancelPendingCommit = schedulePendingCommit (
11811216 commitRoot . bind ( null , root , recoverableErrors , transitions ) ,
11821217 ) ;
1183- markRootSuspended ( root , lanes ) ;
1218+ markRootSuspended ( root , lanes , spawnedLane ) ;
11841219 return ;
11851220 }
11861221 }
11871222
11881223 // Otherwise, commit immediately.
1189- commitRoot ( root , recoverableErrors , transitions ) ;
1224+ commitRoot ( root , recoverableErrors , transitions , spawnedLane ) ;
11901225}
11911226
11921227function isRenderConsistentWithExternalStores ( finishedWork : Fiber ) : boolean {
@@ -1242,7 +1277,11 @@ function isRenderConsistentWithExternalStores(finishedWork: Fiber): boolean {
12421277 return true ;
12431278}
12441279
1245- function markRootSuspended ( root : FiberRoot , suspendedLanes : Lanes ) {
1280+ function markRootSuspended (
1281+ root : FiberRoot ,
1282+ suspendedLanes : Lanes ,
1283+ spawnedLane : Lane ,
1284+ ) {
12461285 // When suspending, we should always exclude lanes that were pinged or (more
12471286 // rarely, since we try to avoid it) updated during the render phase.
12481287 // TODO: Lol maybe there's a better way to factor this besides this
@@ -1252,7 +1291,7 @@ function markRootSuspended(root: FiberRoot, suspendedLanes: Lanes) {
12521291 suspendedLanes ,
12531292 workInProgressRootInterleavedUpdatedLanes ,
12541293 ) ;
1255- markRootSuspended_dontCallThisOneDirectly ( root , suspendedLanes ) ;
1294+ markRootSuspended_dontCallThisOneDirectly ( root , suspendedLanes , spawnedLane ) ;
12561295}
12571296
12581297// This is the entry point for synchronous tasks that don't go
@@ -1302,7 +1341,7 @@ export function performSyncWorkOnRoot(root: FiberRoot, lanes: Lanes): null {
13021341 if ( exitStatus === RootFatalErrored ) {
13031342 const fatalError = workInProgressRootFatalError ;
13041343 prepareFreshStack ( root , NoLanes ) ;
1305- markRootSuspended ( root , lanes ) ;
1344+ markRootSuspended ( root , lanes , NoLane ) ;
13061345 ensureRootIsScheduled ( root ) ;
13071346 throw fatalError ;
13081347 }
@@ -1311,7 +1350,7 @@ export function performSyncWorkOnRoot(root: FiberRoot, lanes: Lanes): null {
13111350 // The render unwound without completing the tree. This happens in special
13121351 // cases where need to exit the current render without producing a
13131352 // consistent tree or committing.
1314- markRootSuspended ( root , lanes ) ;
1353+ markRootSuspended ( root , lanes , NoLane ) ;
13151354 ensureRootIsScheduled ( root ) ;
13161355 return null ;
13171356 }
@@ -1325,6 +1364,7 @@ export function performSyncWorkOnRoot(root: FiberRoot, lanes: Lanes): null {
13251364 root ,
13261365 workInProgressRootRecoverableErrors ,
13271366 workInProgressTransitions ,
1367+ workInProgressDeferredLane ,
13281368 ) ;
13291369
13301370 // Before exiting, make sure there's a callback scheduled for the next
@@ -1537,6 +1577,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
15371577 workInProgressRootInterleavedUpdatedLanes = NoLanes ;
15381578 workInProgressRootRenderPhaseUpdatedLanes = NoLanes ;
15391579 workInProgressRootPingedLanes = NoLanes ;
1580+ workInProgressDeferredLane = NoLane ;
15401581 workInProgressRootConcurrentErrors = null ;
15411582 workInProgressRootRecoverableErrors = null ;
15421583
@@ -1808,9 +1849,9 @@ export function renderDidSuspendDelayIfPossible(): void {
18081849 // Check if there are updates that we skipped tree that might have unblocked
18091850 // this render.
18101851 if (
1811- workInProgressRoot !== null &&
18121852 ( includesNonIdleWork ( workInProgressRootSkippedLanes ) ||
1813- includesNonIdleWork ( workInProgressRootInterleavedUpdatedLanes ) )
1853+ includesNonIdleWork ( workInProgressRootInterleavedUpdatedLanes ) ) &&
1854+ workInProgressRoot !== null
18141855 ) {
18151856 // Mark the current render as suspended so that we switch to working on
18161857 // the updates that were skipped. Usually we only suspend at the end of
@@ -1821,8 +1862,11 @@ export function renderDidSuspendDelayIfPossible(): void {
18211862 // pinged or updated while we were rendering.
18221863 // TODO: Consider unwinding immediately, using the
18231864 // SuspendedOnHydration mechanism.
1824- // $FlowFixMe[incompatible-call] need null check workInProgressRoot
1825- markRootSuspended ( workInProgressRoot , workInProgressRootRenderLanes ) ;
1865+ markRootSuspended (
1866+ workInProgressRoot ,
1867+ workInProgressRootRenderLanes ,
1868+ workInProgressDeferredLane ,
1869+ ) ;
18261870 }
18271871}
18281872
@@ -2592,6 +2636,7 @@ function commitRoot(
25922636 root : FiberRoot ,
25932637 recoverableErrors : null | Array < CapturedValue < mixed >> ,
25942638 transitions : Array < Transition > | null ,
2639+ spawnedLane : Lane ,
25952640) {
25962641 // TODO: This no longer makes any sense. We already wrap the mutation and
25972642 // layout phases. Should be able to remove.
@@ -2606,6 +2651,7 @@ function commitRoot(
26062651 recoverableErrors ,
26072652 transitions ,
26082653 previousUpdateLanePriority ,
2654+ spawnedLane ,
26092655 ) ;
26102656 } finally {
26112657 ReactCurrentBatchConfig . transition = prevTransition ;
@@ -2620,6 +2666,7 @@ function commitRootImpl(
26202666 recoverableErrors : null | Array < CapturedValue < mixed >> ,
26212667 transitions : Array < Transition > | null ,
26222668 renderPriorityLevel : EventPriority ,
2669+ spawnedLane : Lane ,
26232670) {
26242671 do {
26252672 // `flushPassiveEffects` will call `flushSyncUpdateQueue` at the end, which
@@ -2696,7 +2743,7 @@ function commitRootImpl(
26962743 const concurrentlyUpdatedLanes = getConcurrentlyUpdatedLanes ( ) ;
26972744 remainingLanes = mergeLanes ( remainingLanes , concurrentlyUpdatedLanes ) ;
26982745
2699- markRootFinished ( root , remainingLanes ) ;
2746+ markRootFinished ( root , remainingLanes , spawnedLane ) ;
27002747
27012748 if ( root === workInProgressRoot ) {
27022749 // We can reset these now that they are finished.
0 commit comments