99
1010import { REACT_STRICT_MODE_TYPE } from 'shared/ReactSymbols' ;
1111
12- import type { Wakeable } from 'shared/ReactTypes' ;
12+ import type { Wakeable , Thenable } from 'shared/ReactTypes' ;
1313import type { Fiber , FiberRoot } from './ReactInternalTypes' ;
1414import type { Lanes , Lane } from './ReactFiberLane.new' ;
15- import type { SuspenseState } from './ReactFiberSuspenseComponent.new' ;
15+ import type {
16+ SuspenseProps ,
17+ SuspenseState ,
18+ } from './ReactFiberSuspenseComponent.new' ;
1619import type { FunctionComponentUpdateQueue } from './ReactFiberHooks.new' ;
1720import type { EventPriority } from './ReactEventPriorities.new' ;
1821import type {
@@ -271,6 +274,10 @@ import {
271274 isThenableStateResolved ,
272275} from './ReactFiberThenable.new' ;
273276import { schedulePostPaintCallback } from './ReactPostPaintCallback' ;
277+ import {
278+ getSuspenseHandler ,
279+ isBadSuspenseFallback ,
280+ } from './ReactFiberSuspenseContext.new' ;
274281
275282const ceil = Math . ceil ;
276283
@@ -312,7 +319,7 @@ let workInProgressRootRenderLanes: Lanes = NoLanes;
312319opaque type SuspendedReason = 0 | 1 | 2 | 3 | 4 ;
313320const NotSuspended : SuspendedReason = 0 ;
314321const SuspendedOnError : SuspendedReason = 1 ;
315- // const SuspendedOnData: SuspendedReason = 2;
322+ const SuspendedOnData : SuspendedReason = 2 ;
316323const SuspendedOnImmediate : SuspendedReason = 3 ;
317324const SuspendedAndReadyToUnwind : SuspendedReason = 4 ;
318325
@@ -706,6 +713,18 @@ export function scheduleUpdateOnFiber(
706713 }
707714 }
708715
716+ // Check if the work loop is currently suspended and waiting for data to
717+ // finish loading.
718+ if (
719+ workInProgressSuspendedReason === SuspendedOnData &&
720+ root === workInProgressRoot
721+ ) {
722+ // The incoming update might unblock the current render. Interrupt the
723+ // current attempt and restart from the top.
724+ prepareFreshStack ( root , NoLanes ) ;
725+ markRootSuspended ( root , workInProgressRootRenderLanes ) ;
726+ }
727+
709728 // Mark that the root has a pending update.
710729 markRootUpdated ( root , lane , eventTime ) ;
711730
@@ -1130,6 +1149,20 @@ function performConcurrentWorkOnRoot(root, didTimeout) {
11301149 if ( root . callbackNode === originalCallbackNode ) {
11311150 // The task node scheduled for this root is the same one that's
11321151 // currently executed. Need to return a continuation.
1152+ if (
1153+ workInProgressSuspendedReason === SuspendedOnData &&
1154+ workInProgressRoot === root
1155+ ) {
1156+ // Special case: The work loop is currently suspended and waiting for
1157+ // data to resolve. Unschedule the current task.
1158+ //
1159+ // TODO: The factoring is a little weird. Arguably this should be checked
1160+ // in ensureRootIsScheduled instead. I went back and forth, not totally
1161+ // sure yet.
1162+ root . callbackPriority = NoLane ;
1163+ root . callbackNode = null ;
1164+ return null ;
1165+ }
11331166 return performConcurrentWorkOnRoot . bind ( null , root ) ;
11341167 }
11351168 return null ;
@@ -1739,7 +1772,9 @@ function handleThrow(root, thrownValue): void {
17391772 // deprecate the old API in favor of `use`.
17401773 thrownValue = getSuspendedThenable ( ) ;
17411774 workInProgressSuspendedThenableState = getThenableStateAfterSuspending ( ) ;
1742- workInProgressSuspendedReason = SuspendedOnImmediate ;
1775+ workInProgressSuspendedReason = shouldAttemptToSuspendUntilDataResolves ( )
1776+ ? SuspendedOnData
1777+ : SuspendedOnImmediate ;
17431778 } else {
17441779 // This is a regular error. If something earlier in the component already
17451780 // suspended, we must clear the thenable state to unblock the work loop.
@@ -1796,6 +1831,48 @@ function handleThrow(root, thrownValue): void {
17961831 }
17971832}
17981833
1834+ function shouldAttemptToSuspendUntilDataResolves ( ) {
1835+ // TODO: We should be able to move the
1836+ // renderDidSuspend/renderDidSuspendWithDelay logic into this function,
1837+ // instead of repeating it in the complete phase. Or something to that effect.
1838+
1839+ if ( includesOnlyRetries ( workInProgressRootRenderLanes ) ) {
1840+ // We can always wait during a retry.
1841+ return true ;
1842+ }
1843+
1844+ // TODO: We should be able to remove the equivalent check in
1845+ // finishConcurrentRender, and rely just on this one.
1846+ if ( includesOnlyTransitions ( workInProgressRootRenderLanes ) ) {
1847+ const suspenseHandler = getSuspenseHandler ( ) ;
1848+ if ( suspenseHandler !== null && suspenseHandler . tag === SuspenseComponent ) {
1849+ const currentSuspenseHandler = suspenseHandler . alternate ;
1850+ const nextProps : SuspenseProps = suspenseHandler . memoizedProps ;
1851+ if ( isBadSuspenseFallback ( currentSuspenseHandler , nextProps ) ) {
1852+ // The nearest Suspense boundary is already showing content. We should
1853+ // avoid replacing it with a fallback, and instead wait until the
1854+ // data finishes loading.
1855+ return true ;
1856+ } else {
1857+ // This is not a bad fallback condition. We should show a fallback
1858+ // immediately instead of waiting for the data to resolve. This includes
1859+ // when suspending inside new trees.
1860+ return false ;
1861+ }
1862+ }
1863+
1864+ // During a transition, if there is no Suspense boundary (i.e. suspending in
1865+ // the "shell" of an application), or if we're inside a hidden tree, then
1866+ // we should wait until the data finishes loading.
1867+ return true ;
1868+ }
1869+
1870+ // For all other Lanes besides Transitions and Retries, we should not wait
1871+ // for the data to load.
1872+ // TODO: We should wait during Offscreen prerendering, too.
1873+ return false ;
1874+ }
1875+
17991876function pushDispatcher ( container ) {
18001877 prepareRendererToRender ( container ) ;
18011878 const prevDispatcher = ReactCurrentDispatcher . current ;
@@ -2060,7 +2137,7 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
20602137 markRenderStarted ( lanes ) ;
20612138 }
20622139
2063- do {
2140+ outer : do {
20642141 try {
20652142 if (
20662143 workInProgressSuspendedReason !== NotSuspended &&
@@ -2070,19 +2147,48 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
20702147 // replay the suspended component.
20712148 const unitOfWork = workInProgress ;
20722149 const thrownValue = workInProgressThrownValue ;
2073- workInProgressSuspendedReason = NotSuspended ;
2074- workInProgressThrownValue = null ;
20752150 switch ( workInProgressSuspendedReason ) {
20762151 case SuspendedOnError : {
20772152 // Unwind then continue with the normal work loop.
2153+ workInProgressSuspendedReason = NotSuspended ;
2154+ workInProgressThrownValue = null ;
20782155 unwindSuspendedUnitOfWork ( unitOfWork , thrownValue ) ;
20792156 break ;
20802157 }
2158+ case SuspendedOnData : {
2159+ const didResolve =
2160+ workInProgressSuspendedThenableState !== null &&
2161+ isThenableStateResolved ( workInProgressSuspendedThenableState ) ;
2162+ if ( didResolve ) {
2163+ workInProgressSuspendedReason = NotSuspended ;
2164+ workInProgressThrownValue = null ;
2165+ replaySuspendedUnitOfWork ( unitOfWork , thrownValue ) ;
2166+ } else {
2167+ // The work loop is suspended on data. We should wait for it to
2168+ // resolve before continuing to render.
2169+ const thenable : Thenable < mixed > = ( workInProgressThrownValue : any ) ;
2170+ const onResolution = ( ) = > {
2171+ ensureRootIsScheduled ( root , now ( ) ) ;
2172+ } ;
2173+ thenable . then ( onResolution , onResolution ) ;
2174+ break outer ;
2175+ }
2176+ break ;
2177+ }
2178+ case SuspendedOnImmediate : {
2179+ // If this fiber just suspended, it's possible the data is already
2180+ // cached. Yield to the main thread to give it a chance to ping. If
2181+ // it does, we can retry immediately without unwinding the stack.
2182+ workInProgressSuspendedReason = SuspendedAndReadyToUnwind ;
2183+ break outer ;
2184+ }
20812185 default : {
2082- const wasPinged =
2186+ workInProgressSuspendedReason = NotSuspended ;
2187+ workInProgressThrownValue = null ;
2188+ const didResolve =
20832189 workInProgressSuspendedThenableState !== null &&
20842190 isThenableStateResolved ( workInProgressSuspendedThenableState ) ;
2085- if ( wasPinged ) {
2191+ if ( didResolve ) {
20862192 replaySuspendedUnitOfWork ( unitOfWork , thrownValue ) ;
20872193 } else {
20882194 unwindSuspendedUnitOfWork ( unitOfWork , thrownValue ) ;
@@ -2096,12 +2202,6 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
20962202 break ;
20972203 } catch ( thrownValue ) {
20982204 handleThrow ( root , thrownValue ) ;
2099- if ( workInProgressSuspendedThenableState !== null ) {
2100- // If this fiber just suspended, it's possible the data is already
2101- // cached. Yield to the main thread to give it a chance to ping. If
2102- // it does, we can retry immediately without unwinding the stack.
2103- break ;
2104- }
21052205 }
21062206 } while ( true ) ;
21072207 resetContextDependencies ( ) ;
0 commit comments