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 {
@@ -271,6 +274,10 @@ import {
271
274
isThenableStateResolved ,
272
275
} from './ReactFiberThenable.new' ;
273
276
import { schedulePostPaintCallback } from './ReactPostPaintCallback' ;
277
+ import {
278
+ getSuspenseHandler ,
279
+ isBadSuspenseFallback ,
280
+ } from './ReactFiberSuspenseContext.new' ;
274
281
275
282
const ceil = Math . ceil ;
276
283
@@ -312,7 +319,7 @@ let workInProgressRootRenderLanes: Lanes = NoLanes;
312
319
opaque type SuspendedReason = 0 | 1 | 2 | 3 | 4 ;
313
320
const NotSuspended : SuspendedReason = 0 ;
314
321
const SuspendedOnError : SuspendedReason = 1 ;
315
- // const SuspendedOnData: SuspendedReason = 2;
322
+ const SuspendedOnData : SuspendedReason = 2 ;
316
323
const SuspendedOnImmediate : SuspendedReason = 3 ;
317
324
const SuspendedAndReadyToUnwind : SuspendedReason = 4 ;
318
325
@@ -706,6 +713,18 @@ export function scheduleUpdateOnFiber(
706
713
}
707
714
}
708
715
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
+
709
728
// Mark that the root has a pending update.
710
729
markRootUpdated ( root , lane , eventTime ) ;
711
730
@@ -1130,6 +1149,20 @@ function performConcurrentWorkOnRoot(root, didTimeout) {
1130
1149
if ( root . callbackNode === originalCallbackNode ) {
1131
1150
// The task node scheduled for this root is the same one that's
1132
1151
// 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
+ }
1133
1166
return performConcurrentWorkOnRoot . bind ( null , root ) ;
1134
1167
}
1135
1168
return null ;
@@ -1739,7 +1772,9 @@ function handleThrow(root, thrownValue): void {
1739
1772
// deprecate the old API in favor of `use`.
1740
1773
thrownValue = getSuspendedThenable ( ) ;
1741
1774
workInProgressSuspendedThenableState = getThenableStateAfterSuspending ( ) ;
1742
- workInProgressSuspendedReason = SuspendedOnImmediate ;
1775
+ workInProgressSuspendedReason = shouldAttemptToSuspendUntilDataResolves ( )
1776
+ ? SuspendedOnData
1777
+ : SuspendedOnImmediate ;
1743
1778
} else {
1744
1779
// This is a regular error. If something earlier in the component already
1745
1780
// suspended, we must clear the thenable state to unblock the work loop.
@@ -1796,6 +1831,48 @@ function handleThrow(root, thrownValue): void {
1796
1831
}
1797
1832
}
1798
1833
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
+
1799
1876
function pushDispatcher ( container ) {
1800
1877
prepareRendererToRender ( container ) ;
1801
1878
const prevDispatcher = ReactCurrentDispatcher . current ;
@@ -2060,7 +2137,7 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
2060
2137
markRenderStarted ( lanes ) ;
2061
2138
}
2062
2139
2063
- do {
2140
+ outer : do {
2064
2141
try {
2065
2142
if (
2066
2143
workInProgressSuspendedReason !== NotSuspended &&
@@ -2070,19 +2147,48 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
2070
2147
// replay the suspended component.
2071
2148
const unitOfWork = workInProgress ;
2072
2149
const thrownValue = workInProgressThrownValue ;
2073
- workInProgressSuspendedReason = NotSuspended ;
2074
- workInProgressThrownValue = null ;
2075
2150
switch ( workInProgressSuspendedReason ) {
2076
2151
case SuspendedOnError : {
2077
2152
// Unwind then continue with the normal work loop.
2153
+ workInProgressSuspendedReason = NotSuspended ;
2154
+ workInProgressThrownValue = null ;
2078
2155
unwindSuspendedUnitOfWork ( unitOfWork , thrownValue ) ;
2079
2156
break ;
2080
2157
}
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
+ }
2081
2185
default : {
2082
- const wasPinged =
2186
+ workInProgressSuspendedReason = NotSuspended ;
2187
+ workInProgressThrownValue = null ;
2188
+ const didResolve =
2083
2189
workInProgressSuspendedThenableState !== null &&
2084
2190
isThenableStateResolved ( workInProgressSuspendedThenableState ) ;
2085
- if ( wasPinged ) {
2191
+ if ( didResolve ) {
2086
2192
replaySuspendedUnitOfWork ( unitOfWork , thrownValue ) ;
2087
2193
} else {
2088
2194
unwindSuspendedUnitOfWork ( unitOfWork , thrownValue ) ;
@@ -2096,12 +2202,6 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
2096
2202
break ;
2097
2203
} catch ( thrownValue ) {
2098
2204
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
- }
2105
2205
}
2106
2206
} while ( true ) ;
2107
2207
resetContextDependencies ( ) ;
0 commit comments