@@ -69,7 +69,7 @@ function _assertThisInitialized(self) {
69
69
return self;
70
70
}
71
71
72
- var ReactVersion = "18.3.0-www-classic-48274a43a -20230104";
72
+ var ReactVersion = "18.3.0-www-classic-c2d655207 -20230104";
73
73
74
74
var LegacyRoot = 0;
75
75
var ConcurrentRoot = 1;
@@ -6864,71 +6864,68 @@ function isCurrentTreeHidden() {
6864
6864
6865
6865
// suspends, i.e. it's the nearest `catch` block on the stack.
6866
6866
6867
- var suspenseHandlerStackCursor = createCursor(null);
6868
-
6869
- function shouldAvoidedBoundaryCapture(workInProgress, handlerOnStack, props) {
6870
- {
6871
- // If the parent is already showing content, and we're not inside a hidden
6872
- // tree, then we should show the avoided fallback.
6873
- if (handlerOnStack.alternate !== null && !isCurrentTreeHidden()) {
6874
- return true;
6875
- } // If the handler on the stack is also an avoided boundary, then we should
6876
- // favor this inner one.
6877
-
6878
- if (
6879
- handlerOnStack.tag === SuspenseComponent &&
6880
- handlerOnStack.memoizedProps.unstable_avoidThisFallback === true
6881
- ) {
6882
- return true;
6883
- } // If this avoided boundary is dehydrated, then it should capture.
6884
-
6885
- var suspenseState = workInProgress.memoizedState;
6886
-
6887
- if (suspenseState !== null && suspenseState.dehydrated !== null) {
6888
- return true;
6889
- }
6890
- } // If none of those cases apply, then we should avoid this fallback and show
6891
- // the outer one instead.
6892
-
6893
- return false;
6867
+ var suspenseHandlerStackCursor = createCursor(null); // Represents the outermost boundary that is not visible in the current tree.
6868
+ // Everything above this is the "shell". When this is null, it means we're
6869
+ // rendering in the shell of the app. If it's non-null, it means we're rendering
6870
+ // deeper than the shell, inside a new tree that wasn't already visible.
6871
+ //
6872
+ // The main way we use this concept is to determine whether showing a fallback
6873
+ // would result in a desirable or undesirable loading state. Activing a fallback
6874
+ // in the shell is considered an undersirable loading state, because it would
6875
+ // mean hiding visible (albeit stale) content in the current tree — we prefer to
6876
+ // show the stale content, rather than switch to a fallback. But showing a
6877
+ // fallback in a new tree is fine, because there's no stale content to
6878
+ // prefer instead.
6879
+
6880
+ var shellBoundary = null;
6881
+ function getShellBoundary() {
6882
+ return shellBoundary;
6894
6883
}
6884
+ function pushPrimaryTreeSuspenseHandler(handler) {
6885
+ // TODO: Pass as argument
6886
+ var current = handler.alternate;
6887
+ var props = handler.pendingProps; // Experimental feature: Some Suspense boundaries are marked as having an
6888
+ // undesirable fallback state. These have special behavior where we only
6889
+ // activate the fallback if there's no other boundary on the stack that we can
6890
+ // use instead.
6895
6891
6896
- function isBadSuspenseFallback(current, nextProps) {
6897
- // Check if this is a "bad" fallback state or a good one. A bad fallback state
6898
- // is one that we only show as a last resort; if this is a transition, we'll
6899
- // block it from displaying, and wait for more data to arrive.
6900
- if (current !== null) {
6901
- var prevState = current.memoizedState;
6902
- var isShowingFallback = prevState !== null;
6903
-
6904
- if (!isShowingFallback && !isCurrentTreeHidden()) {
6905
- // It's bad to switch to a fallback if content is already visible
6906
- return true;
6892
+ if (
6893
+ props.unstable_avoidThisFallback === true && // If an avoided boundary is already visible, it behaves identically to
6894
+ // a regular Suspense boundary.
6895
+ (current === null || isCurrentTreeHidden())
6896
+ ) {
6897
+ if (shellBoundary === null) {
6898
+ // We're rendering in the shell. There's no parent Suspense boundary that
6899
+ // can provide a desirable fallback state. We'll use this boundary.
6900
+ push(suspenseHandlerStackCursor, handler, handler); // However, because this is not a desirable fallback, the children are
6901
+ // still considered part of the shell. So we intentionally don't assign
6902
+ // to `shellBoundary`.
6903
+ } else {
6904
+ // There's already a parent Suspense boundary that can provide a desirable
6905
+ // fallback state. Prefer that one.
6906
+ var handlerOnStack = suspenseHandlerStackCursor.current;
6907
+ push(suspenseHandlerStackCursor, handlerOnStack, handler);
6907
6908
}
6908
- }
6909
6909
6910
- if (nextProps.unstable_avoidThisFallback === true) {
6911
- // Experimental: Some fallbacks are always bad
6912
- return true;
6913
- }
6910
+ return;
6911
+ } // TODO: If the parent Suspense handler already suspended, there's no reason
6912
+ // to push a nested Suspense handler, because it will get replaced by the
6913
+ // outer fallback, anyway. Consider this as a future optimization.
6914
6914
6915
- return false;
6916
- }
6917
- function pushPrimaryTreeSuspenseHandler(handler) {
6918
- var props = handler.pendingProps;
6919
- var handlerOnStack = suspenseHandlerStackCursor.current;
6915
+ push(suspenseHandlerStackCursor, handler, handler);
6920
6916
6921
- if (
6922
- props.unstable_avoidThisFallback === true &&
6923
- handlerOnStack !== null &&
6924
- !shouldAvoidedBoundaryCapture(handler, handlerOnStack)
6925
- ) {
6926
- // This boundary should not capture if something suspends. Reuse the
6927
- // existing handler on the stack.
6928
- push(suspenseHandlerStackCursor, handlerOnStack, handler);
6929
- } else {
6930
- // Push this handler onto the stack.
6931
- push(suspenseHandlerStackCursor, handler, handler);
6917
+ if (shellBoundary === null) {
6918
+ if (current === null || isCurrentTreeHidden()) {
6919
+ // This boundary is not visible in the current UI.
6920
+ shellBoundary = handler;
6921
+ } else {
6922
+ var prevState = current.memoizedState;
6923
+
6924
+ if (prevState !== null) {
6925
+ // This boundary is showing a fallback in the current UI.
6926
+ shellBoundary = handler;
6927
+ }
6928
+ }
6932
6929
}
6933
6930
}
6934
6931
function pushFallbackTreeSuspenseHandler(fiber) {
@@ -6940,6 +6937,21 @@ function pushFallbackTreeSuspenseHandler(fiber) {
6940
6937
function pushOffscreenSuspenseHandler(fiber) {
6941
6938
if (fiber.tag === OffscreenComponent) {
6942
6939
push(suspenseHandlerStackCursor, fiber, fiber);
6940
+
6941
+ if (shellBoundary !== null);
6942
+ else {
6943
+ var current = fiber.alternate;
6944
+
6945
+ if (current !== null) {
6946
+ var prevState = current.memoizedState;
6947
+
6948
+ if (prevState !== null) {
6949
+ // This is the first boundary in the stack that's already showing
6950
+ // a fallback. So everything outside is considered the shell.
6951
+ shellBoundary = fiber;
6952
+ }
6953
+ }
6954
+ }
6943
6955
} else {
6944
6956
// This is a LegacyHidden component.
6945
6957
reuseSuspenseHandlerOnStack(fiber);
@@ -6953,6 +6965,11 @@ function getSuspenseHandler() {
6953
6965
}
6954
6966
function popSuspenseHandler(fiber) {
6955
6967
pop(suspenseHandlerStackCursor, fiber);
6968
+
6969
+ if (shellBoundary === fiber) {
6970
+ // Popping back into the shell.
6971
+ shellBoundary = null;
6972
+ }
6956
6973
} // SuspenseList context
6957
6974
// TODO: Move to a separate module? We may change the SuspenseList
6958
6975
// implementation to hide/show in the commit phase, anyway.
@@ -12368,13 +12385,49 @@ function throwException(
12368
12385
logComponentSuspended(name, wakeable);
12369
12386
}
12370
12387
}
12371
- } // Schedule the nearest Suspense to re-render the timed out view .
12388
+ } // Mark the nearest Suspense boundary to switch to rendering a fallback .
12372
12389
12373
12390
var suspenseBoundary = getSuspenseHandler();
12374
12391
12375
12392
if (suspenseBoundary !== null) {
12376
12393
switch (suspenseBoundary.tag) {
12377
12394
case SuspenseComponent: {
12395
+ // If this suspense boundary is not already showing a fallback, mark
12396
+ // the in-progress render as suspended. We try to perform this logic
12397
+ // as soon as soon as possible during the render phase, so the work
12398
+ // loop can know things like whether it's OK to switch to other tasks,
12399
+ // or whether it can wait for data to resolve before continuing.
12400
+ // TODO: Most of these checks are already performed when entering a
12401
+ // Suspense boundary. We should track the information on the stack so
12402
+ // we don't have to recompute it on demand. This would also allow us
12403
+ // to unify with `use` which needs to perform this logic even sooner,
12404
+ // before `throwException` is called.
12405
+ if (sourceFiber.mode & ConcurrentMode) {
12406
+ if (getShellBoundary() === null) {
12407
+ // Suspended in the "shell" of the app. This is an undesirable
12408
+ // loading state. We should avoid committing this tree.
12409
+ renderDidSuspendDelayIfPossible();
12410
+ } else {
12411
+ // If we suspended deeper than the shell, we don't need to delay
12412
+ // the commmit. However, we still call renderDidSuspend if this is
12413
+ // a new boundary, to tell the work loop that a new fallback has
12414
+ // appeared during this render.
12415
+ // TODO: Theoretically we should be able to delete this branch.
12416
+ // It's currently used for two things: 1) to throttle the
12417
+ // appearance of successive loading states, and 2) in
12418
+ // SuspenseList, to determine whether the children include any
12419
+ // pending fallbacks. For 1, we should apply throttling to all
12420
+ // retries, not just ones that render an additional fallback. For
12421
+ // 2, we should check subtreeFlags instead. Then we can delete
12422
+ // this branch.
12423
+ var current = suspenseBoundary.alternate;
12424
+
12425
+ if (current === null) {
12426
+ renderDidSuspend();
12427
+ }
12428
+ }
12429
+ }
12430
+
12378
12431
suspenseBoundary.flags &= ~ForceClientRender;
12379
12432
markSuspenseBoundaryShouldCapture(
12380
12433
suspenseBoundary,
@@ -18001,24 +18054,7 @@ function completeWork(current, workInProgress, renderLanes) {
18001
18054
18002
18055
if (nextDidTimeout) {
18003
18056
var _offscreenFiber2 = workInProgress.child;
18004
- _offscreenFiber2.flags |= Visibility; // TODO: This will still suspend a synchronous tree if anything
18005
- // in the concurrent tree already suspended during this render.
18006
- // This is a known bug.
18007
-
18008
- if ((workInProgress.mode & ConcurrentMode) !== NoMode) {
18009
- // TODO: Move this back to throwException because this is too late
18010
- // if this is a large tree which is common for initial loads. We
18011
- // don't know if we should restart a render or not until we get
18012
- // this marker, and this is too late.
18013
- // If this render already had a ping or lower pri updates,
18014
- // and this is the first time we know we're going to suspend we
18015
- // should be able to immediately restart from within throwException.
18016
- if (isBadSuspenseFallback(current, newProps)) {
18017
- renderDidSuspendDelayIfPossible();
18018
- } else {
18019
- renderDidSuspend();
18020
- }
18021
- }
18057
+ _offscreenFiber2.flags |= Visibility;
18022
18058
}
18023
18059
}
18024
18060
@@ -24294,16 +24330,11 @@ function handleThrow(root, thrownValue) {
24294
24330
}
24295
24331
24296
24332
function shouldAttemptToSuspendUntilDataResolves() {
24297
- // TODO: We should be able to move the
24298
- // renderDidSuspend/renderDidSuspendDelayIfPossible logic into this function,
24299
- // instead of repeating it in the complete phase. Or something to that effect.
24300
- if (includesOnlyRetries(workInProgressRootRenderLanes)) {
24301
- // We can always wait during a retry.
24302
- return true;
24303
- } // Check if there are other pending updates that might possibly unblock this
24333
+ // Check if there are other pending updates that might possibly unblock this
24304
24334
// component from suspending. This mirrors the check in
24305
24335
// renderDidSuspendDelayIfPossible. We should attempt to unify them somehow.
24306
-
24336
+ // TODO: Consider unwinding immediately, using the
24337
+ // SuspendedOnHydration mechanism.
24307
24338
if (
24308
24339
includesNonIdleWork(workInProgressRootSkippedLanes) ||
24309
24340
includesNonIdleWork(workInProgressRootInterleavedUpdatedLanes)
@@ -24315,28 +24346,22 @@ function shouldAttemptToSuspendUntilDataResolves() {
24315
24346
// finishConcurrentRender, and rely just on this one.
24316
24347
24317
24348
if (includesOnlyTransitions(workInProgressRootRenderLanes)) {
24318
- var suspenseHandler = getSuspenseHandler();
24319
-
24320
- if (suspenseHandler !== null && suspenseHandler.tag === SuspenseComponent) {
24321
- var currentSuspenseHandler = suspenseHandler.alternate ;
24322
- var nextProps = suspenseHandler.memoizedProps;
24349
+ // If we're rendering inside the "shell" of the app, it's better to suspend
24350
+ // rendering and wait for the data to resolve. Otherwise, we should switch
24351
+ // to a fallback and continue rendering.
24352
+ return getShellBoundary() === null ;
24353
+ }
24323
24354
24324
- if (isBadSuspenseFallback(currentSuspenseHandler, nextProps)) {
24325
- // The nearest Suspense boundary is already showing content. We should
24326
- // avoid replacing it with a fallback, and instead wait until the
24327
- // data finishes loading.
24328
- return true;
24329
- } else {
24330
- // This is not a bad fallback condition. We should show a fallback
24331
- // immediately instead of waiting for the data to resolve. This includes
24332
- // when suspending inside new trees.
24333
- return false;
24334
- }
24335
- } // During a transition, if there is no Suspense boundary (i.e. suspending in
24336
- // the "shell" of an application), or if we're inside a hidden tree, then
24337
- // we should wait until the data finishes loading.
24355
+ var handler = getSuspenseHandler();
24338
24356
24339
- return true;
24357
+ if (handler === null);
24358
+ else {
24359
+ if (includesOnlyRetries(workInProgressRootRenderLanes)) {
24360
+ // During a retry, we can suspend rendering if the nearest Suspense boundary
24361
+ // is the boundary of the "shell", because we're guaranteed not to block
24362
+ // any new content from appearing.
24363
+ return handler === getShellBoundary();
24364
+ }
24340
24365
} // For all other Lanes besides Transitions and Retries, we should not wait
24341
24366
// for the data to load.
24342
24367
// TODO: We should wait during Offscreen prerendering, too.
@@ -24406,6 +24431,8 @@ function renderDidSuspendDelayIfPossible() {
24406
24431
// (inside this function), since by suspending at the end of the render
24407
24432
// phase introduces a potential mistake where we suspend lanes that were
24408
24433
// pinged or updated while we were rendering.
24434
+ // TODO: Consider unwinding immediately, using the
24435
+ // SuspendedOnHydration mechanism.
24409
24436
markRootSuspended$1(workInProgressRoot, workInProgressRootRenderLanes);
24410
24437
}
24411
24438
}
@@ -24621,6 +24648,10 @@ function renderRootConcurrent(root, lanes) {
24621
24648
break;
24622
24649
} // The work loop is suspended on data. We should wait for it to
24623
24650
// resolve before continuing to render.
24651
+ // TODO: Handle the case where the promise resolves synchronously.
24652
+ // Usually this is handled when we instrument the promise to add a
24653
+ // `status` field, but if the promise already has a status, we won't
24654
+ // have added a listener until right here.
24624
24655
24625
24656
var onResolution = function() {
24626
24657
ensureRootIsScheduled(root, now());
0 commit comments