9
9
10
10
import { REACT_STRICT_MODE_TYPE } from 'shared/ReactSymbols' ;
11
11
12
- import type { Wakeable } from 'shared/ReactTypes' ;
12
+ import type { Wakeable , Thenable } from 'shared/ReactTypes' ;
13
13
import type { Fiber , FiberRoot } from './ReactInternalTypes' ;
14
14
import type { Lanes , Lane } from './ReactFiberLane.new' ;
15
- import type { SuspenseState } from './ReactFiberSuspenseComponent.new' ;
15
+ import type {
16
+ SuspenseProps ,
17
+ SuspenseState ,
18
+ } from './ReactFiberSuspenseComponent.new' ;
16
19
import type { FunctionComponentUpdateQueue } from './ReactFiberHooks.new' ;
17
20
import type { EventPriority } from './ReactEventPriorities.new' ;
18
21
import type {
@@ -272,6 +275,10 @@ import {
272
275
isThenableStateResolved ,
273
276
} from './ReactFiberThenable.new' ;
274
277
import { schedulePostPaintCallback } from './ReactPostPaintCallback' ;
278
+ import {
279
+ getSuspenseHandler ,
280
+ isBadSuspenseFallback ,
281
+ } from './ReactFiberSuspenseContext.new' ;
275
282
276
283
const ceil = Math . ceil ;
277
284
@@ -313,7 +320,7 @@ let workInProgressRootRenderLanes: Lanes = NoLanes;
313
320
opaque type SuspendedReason = 0 | 1 | 2 | 3 | 4 ;
314
321
const NotSuspended : SuspendedReason = 0 ;
315
322
const SuspendedOnError : SuspendedReason = 1 ;
316
- // const SuspendedOnData: SuspendedReason = 2;
323
+ const SuspendedOnData : SuspendedReason = 2 ;
317
324
const SuspendedOnImmediate : SuspendedReason = 3 ;
318
325
const SuspendedAndReadyToUnwind : SuspendedReason = 4 ;
319
326
@@ -707,6 +714,18 @@ export function scheduleUpdateOnFiber(
707
714
}
708
715
}
709
716
717
+ // Check if the work loop is currently suspended and waiting for data to
718
+ // finish loading.
719
+ if (
720
+ workInProgressSuspendedReason === SuspendedOnData &&
721
+ root === workInProgressRoot
722
+ ) {
723
+ // The incoming update might unblock the current render. Interrupt the
724
+ // current attempt and restart from the top.
725
+ prepareFreshStack ( root , NoLanes ) ;
726
+ markRootSuspended ( root , workInProgressRootRenderLanes ) ;
727
+ }
728
+
710
729
// Mark that the root has a pending update.
711
730
markRootUpdated ( root , lane , eventTime ) ;
712
731
@@ -1131,6 +1150,17 @@ function performConcurrentWorkOnRoot(root, didTimeout) {
1131
1150
if ( root . callbackNode === originalCallbackNode ) {
1132
1151
// The task node scheduled for this root is the same one that's
1133
1152
// currently executed. Need to return a continuation.
1153
+ if ( workInProgressSuspendedReason === SuspendedOnData ) {
1154
+ // Special case: The work loop is currently suspended and waiting for
1155
+ // data to resolve. Unschedule the current task.
1156
+ //
1157
+ // TODO: The factoring is a little weird. Arguably this should be checked
1158
+ // in ensureRootIsScheduled instead. I went back and forth, not totally
1159
+ // sure yet.
1160
+ root . callbackPriority = NoLane ;
1161
+ root . callbackNode = null ;
1162
+ return null ;
1163
+ }
1134
1164
return performConcurrentWorkOnRoot . bind ( null , root ) ;
1135
1165
}
1136
1166
return null ;
@@ -1740,7 +1770,9 @@ function handleThrow(root, thrownValue): void {
1740
1770
// deprecate the old API in favor of `use`.
1741
1771
thrownValue = getSuspendedThenable ( ) ;
1742
1772
workInProgressSuspendedThenableState = getThenableStateAfterSuspending ( ) ;
1743
- workInProgressSuspendedReason = SuspendedOnImmediate ;
1773
+ workInProgressSuspendedReason = shouldAttemptToSuspendUntilDataResolves ( )
1774
+ ? SuspendedOnData
1775
+ : SuspendedOnImmediate ;
1744
1776
} else {
1745
1777
// This is a regular error. If something earlier in the component already
1746
1778
// suspended, we must clear the thenable state to unblock the work loop.
@@ -1797,6 +1829,48 @@ function handleThrow(root, thrownValue): void {
1797
1829
}
1798
1830
}
1799
1831
1832
+ function shouldAttemptToSuspendUntilDataResolves ( ) {
1833
+ // TODO: We should be able to move the
1834
+ // renderDidSuspend/renderDidSuspendWithDelay logic into this function,
1835
+ // instead of repeating it in the complete phase. Or something to that effect.
1836
+
1837
+ if ( includesOnlyRetries ( workInProgressRootRenderLanes ) ) {
1838
+ // We can always wait during a retry.
1839
+ return true ;
1840
+ }
1841
+
1842
+ // TODO: We should be able to remove the equivalent check in
1843
+ // finishConcurrentRender, and rely just on this one.
1844
+ if ( includesOnlyTransitions ( workInProgressRootRenderLanes ) ) {
1845
+ const suspenseHandler = getSuspenseHandler ( ) ;
1846
+ if ( suspenseHandler !== null && suspenseHandler . tag === SuspenseComponent ) {
1847
+ const currentSuspenseHandler = suspenseHandler . alternate ;
1848
+ const nextProps : SuspenseProps = suspenseHandler . memoizedProps ;
1849
+ if ( isBadSuspenseFallback ( currentSuspenseHandler , nextProps ) ) {
1850
+ // The nearest Suspense boundary is already showing content. We should
1851
+ // avoid replacing it with a fallback, and instead wait until the
1852
+ // data finishes loading.
1853
+ return true ;
1854
+ } else {
1855
+ // This is not a bad fallback condition. We should show a fallback
1856
+ // immediately instead of waiting for the data to resolve. This includes
1857
+ // when suspending inside new trees.
1858
+ return false ;
1859
+ }
1860
+ }
1861
+
1862
+ // During a transition, if there is no Suspense boundary (i.e. suspending in
1863
+ // the "shell" of an application), or if we're inside a hidden tree, then
1864
+ // we should wait until the data finishes loading.
1865
+ return true ;
1866
+ }
1867
+
1868
+ // For all other Lanes besides Transitions and Retries, we should not wait
1869
+ // for the data to load.
1870
+ // TODO: We should wait during Offscreen prerendering, too.
1871
+ return false ;
1872
+ }
1873
+
1800
1874
function pushDispatcher ( container ) {
1801
1875
prepareRendererToRender ( container ) ;
1802
1876
const prevDispatcher = ReactCurrentDispatcher . current ;
@@ -2061,7 +2135,7 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
2061
2135
markRenderStarted ( lanes ) ;
2062
2136
}
2063
2137
2064
- do {
2138
+ outer : do {
2065
2139
try {
2066
2140
if (
2067
2141
workInProgressSuspendedReason !== NotSuspended &&
@@ -2071,19 +2145,48 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
2071
2145
// replay the suspended component.
2072
2146
const unitOfWork = workInProgress ;
2073
2147
const thrownValue = workInProgressThrownValue ;
2074
- workInProgressSuspendedReason = NotSuspended ;
2075
- workInProgressThrownValue = null ;
2076
2148
switch ( workInProgressSuspendedReason ) {
2077
2149
case SuspendedOnError : {
2078
2150
// Unwind then continue with the normal work loop.
2151
+ workInProgressSuspendedReason = NotSuspended ;
2152
+ workInProgressThrownValue = null ;
2079
2153
unwindSuspendedUnitOfWork ( unitOfWork , thrownValue ) ;
2080
2154
break ;
2081
2155
}
2156
+ case SuspendedOnData : {
2157
+ const didResolve =
2158
+ workInProgressSuspendedThenableState !== null &&
2159
+ isThenableStateResolved ( workInProgressSuspendedThenableState ) ;
2160
+ if ( didResolve ) {
2161
+ workInProgressSuspendedReason = NotSuspended ;
2162
+ workInProgressThrownValue = null ;
2163
+ replaySuspendedUnitOfWork ( unitOfWork , thrownValue ) ;
2164
+ } else {
2165
+ // The work loop is suspended on data. We should wait for it to
2166
+ // resolve before continuing to render.
2167
+ const thenable : Thenable < mixed > = ( workInProgressThrownValue : any ) ;
2168
+ const onResolution = ( ) = > {
2169
+ ensureRootIsScheduled ( root , now ( ) ) ;
2170
+ } ;
2171
+ thenable . then ( onResolution , onResolution ) ;
2172
+ break outer ;
2173
+ }
2174
+ break ;
2175
+ }
2176
+ case SuspendedOnImmediate : {
2177
+ // If this fiber just suspended, it's possible the data is already
2178
+ // cached. Yield to the main thread to give it a chance to ping. If
2179
+ // it does, we can retry immediately without unwinding the stack.
2180
+ workInProgressSuspendedReason = SuspendedAndReadyToUnwind ;
2181
+ break outer ;
2182
+ }
2082
2183
default : {
2083
- const wasPinged =
2184
+ workInProgressSuspendedReason = NotSuspended ;
2185
+ workInProgressThrownValue = null ;
2186
+ const didResolve =
2084
2187
workInProgressSuspendedThenableState !== null &&
2085
2188
isThenableStateResolved ( workInProgressSuspendedThenableState ) ;
2086
- if ( wasPinged ) {
2189
+ if ( didResolve ) {
2087
2190
replaySuspendedUnitOfWork ( unitOfWork , thrownValue ) ;
2088
2191
} else {
2089
2192
unwindSuspendedUnitOfWork ( unitOfWork , thrownValue ) ;
@@ -2097,12 +2200,6 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
2097
2200
break ;
2098
2201
} catch ( thrownValue ) {
2099
2202
handleThrow ( root , thrownValue ) ;
2100
- if ( workInProgressSuspendedThenableState !== null ) {
2101
- // If this fiber just suspended, it's possible the data is already
2102
- // cached. Yield to the main thread to give it a chance to ping. If
2103
- // it does, we can retry immediately without unwinding the stack.
2104
- break ;
2105
- }
2106
2203
}
2107
2204
} while ( true ) ;
2108
2205
resetContextDependencies ( ) ;
0 commit comments