9
10
import {REACT_STRICT_MODE_TYPE} from 'shared/ReactSymbols';
11
12
-import type {Wakeable} from 'shared/ReactTypes';
12
+import type {Wakeable, Thenable} from 'shared/ReactTypes';
13
import type {Fiber, FiberRoot} from './ReactInternalTypes';
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';
19
import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.new';
20
import type {EventPriority} from './ReactEventPriorities.new';
21
import type {
274
isThenableStateResolved,
275
} from './ReactFiberThenable.new';
276
import {schedulePostPaintCallback} from './ReactPostPaintCallback';
277
+import {
278
+ getSuspenseHandler,
279
+ isBadSuspenseFallback,
280
+} from './ReactFiberSuspenseContext.new';
281
282
const ceil = Math.ceil;
283
319
opaque type SuspendedReason = 0 | 1 | 2 | 3 | 4;
320
const NotSuspended: SuspendedReason = 0;
321
const SuspendedOnError: SuspendedReason = 1;
315
-// const SuspendedOnData: SuspendedReason = 2;
322
+const SuspendedOnData: SuspendedReason = 2;
323
const SuspendedOnImmediate: SuspendedReason = 3;
324
const SuspendedAndReadyToUnwind: SuspendedReason = 4;
325
713
}
714
}
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
+
728
// Mark that the root has a pending update.
729
markRootUpdated(root, lane, eventTime);
730
1149
if (root.callbackNode === originalCallbackNode) {
1150
// The task node scheduled for this root is the same one that's
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
+ }
1166
return performConcurrentWorkOnRoot.bind(null, root);
1167
}
1168
return null;
1772
// deprecate the old API in favor of `use`.
1773
thrownValue = getSuspendedThenable();
1774
workInProgressSuspendedThenableState = getThenableStateAfterSuspending();
1742
- workInProgressSuspendedReason = SuspendedOnImmediate;
1775
+ workInProgressSuspendedReason = shouldAttemptToSuspendUntilDataResolves()
1776
+ ? SuspendedOnData
1777
+ : SuspendedOnImmediate;
1778
} else {
1779
// This is a regular error. If something earlier in the component already
1780
// suspended, we must clear the thenable state to unblock the work loop.
1831
}
1832
}
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
+
1876
function pushDispatcher(container) {
1877
prepareRendererToRender(container);
1878
const prevDispatcher = ReactCurrentDispatcher.current;
2137
markRenderStarted(lanes);
2138
}
2139
2063
- do {
2140
+ outer: do {
2141
try {
2142
if (
2143
workInProgressSuspendedReason !== NotSuspended &&
2147
// replay the suspended component.
2148
const unitOfWork = workInProgress;
2149
const thrownValue = workInProgressThrownValue;
2073
- workInProgressSuspendedReason = NotSuspended;
2074
- workInProgressThrownValue = null;
2150
switch (workInProgressSuspendedReason) {
2151
case SuspendedOnError: {
2152
// Unwind then continue with the normal work loop.
2153
+ workInProgressSuspendedReason = NotSuspended;
2154
+ workInProgressThrownValue = null;
2155
unwindSuspendedUnitOfWork(unitOfWork, thrownValue);
2156
break;
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
+ }
2185
default: {
2082
- const wasPinged =
2186
+ workInProgressSuspendedReason = NotSuspended;
2187
+ workInProgressThrownValue = null;
2188
+ const didResolve =
2189
workInProgressSuspendedThenableState !== null &&
2190
isThenableStateResolved(workInProgressSuspendedThenableState);
2085
- if (wasPinged) {
2191
+ if (didResolve) {
2192
replaySuspendedUnitOfWork(unitOfWork, thrownValue);
2193
} else {
2194
unwindSuspendedUnitOfWork(unitOfWork, thrownValue);
2202
break;
2203
} catch (thrownValue) {
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
- }
2205
}
2206
} while (true);
2207
resetContextDependencies();