@@ -69,7 +69,7 @@ function _assertThisInitialized(self) {
6969 return self;
7070}
7171
72- var ReactVersion = "18.3.0-www-classic-48274a43a -20230104";
72+ var ReactVersion = "18.3.0-www-classic-c2d655207 -20230104";
7373
7474var LegacyRoot = 0;
7575var ConcurrentRoot = 1;
@@ -6864,71 +6864,68 @@ function isCurrentTreeHidden() {
68646864
68656865// suspends, i.e. it's the nearest `catch` block on the stack.
68666866
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;
68946883}
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.
68956891
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);
69076908 }
6908- }
69096909
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.
69146914
6915- return false;
6916- }
6917- function pushPrimaryTreeSuspenseHandler(handler) {
6918- var props = handler.pendingProps;
6919- var handlerOnStack = suspenseHandlerStackCursor.current;
6915+ push(suspenseHandlerStackCursor, handler, handler);
69206916
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+ }
69326929 }
69336930}
69346931function pushFallbackTreeSuspenseHandler(fiber) {
@@ -6940,6 +6937,21 @@ function pushFallbackTreeSuspenseHandler(fiber) {
69406937function pushOffscreenSuspenseHandler(fiber) {
69416938 if (fiber.tag === OffscreenComponent) {
69426939 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+ }
69436955 } else {
69446956 // This is a LegacyHidden component.
69456957 reuseSuspenseHandlerOnStack(fiber);
@@ -6953,6 +6965,11 @@ function getSuspenseHandler() {
69536965}
69546966function popSuspenseHandler(fiber) {
69556967 pop(suspenseHandlerStackCursor, fiber);
6968+
6969+ if (shellBoundary === fiber) {
6970+ // Popping back into the shell.
6971+ shellBoundary = null;
6972+ }
69566973} // SuspenseList context
69576974// TODO: Move to a separate module? We may change the SuspenseList
69586975// implementation to hide/show in the commit phase, anyway.
@@ -12368,13 +12385,49 @@ function throwException(
1236812385 logComponentSuspended(name, wakeable);
1236912386 }
1237012387 }
12371- } // Schedule the nearest Suspense to re-render the timed out view .
12388+ } // Mark the nearest Suspense boundary to switch to rendering a fallback .
1237212389
1237312390 var suspenseBoundary = getSuspenseHandler();
1237412391
1237512392 if (suspenseBoundary !== null) {
1237612393 switch (suspenseBoundary.tag) {
1237712394 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+
1237812431 suspenseBoundary.flags &= ~ForceClientRender;
1237912432 markSuspenseBoundaryShouldCapture(
1238012433 suspenseBoundary,
@@ -18001,24 +18054,7 @@ function completeWork(current, workInProgress, renderLanes) {
1800118054
1800218055 if (nextDidTimeout) {
1800318056 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;
1802218058 }
1802318059 }
1802418060
@@ -24294,16 +24330,11 @@ function handleThrow(root, thrownValue) {
2429424330}
2429524331
2429624332function 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
2430424334 // component from suspending. This mirrors the check in
2430524335 // renderDidSuspendDelayIfPossible. We should attempt to unify them somehow.
24306-
24336+ // TODO: Consider unwinding immediately, using the
24337+ // SuspendedOnHydration mechanism.
2430724338 if (
2430824339 includesNonIdleWork(workInProgressRootSkippedLanes) ||
2430924340 includesNonIdleWork(workInProgressRootInterleavedUpdatedLanes)
@@ -24315,28 +24346,22 @@ function shouldAttemptToSuspendUntilDataResolves() {
2431524346 // finishConcurrentRender, and rely just on this one.
2431624347
2431724348 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+ }
2432324354
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();
2433824356
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+ }
2434024365 } // For all other Lanes besides Transitions and Retries, we should not wait
2434124366 // for the data to load.
2434224367 // TODO: We should wait during Offscreen prerendering, too.
@@ -24406,6 +24431,8 @@ function renderDidSuspendDelayIfPossible() {
2440624431 // (inside this function), since by suspending at the end of the render
2440724432 // phase introduces a potential mistake where we suspend lanes that were
2440824433 // pinged or updated while we were rendering.
24434+ // TODO: Consider unwinding immediately, using the
24435+ // SuspendedOnHydration mechanism.
2440924436 markRootSuspended$1(workInProgressRoot, workInProgressRootRenderLanes);
2441024437 }
2441124438}
@@ -24621,6 +24648,10 @@ function renderRootConcurrent(root, lanes) {
2462124648 break;
2462224649 } // The work loop is suspended on data. We should wait for it to
2462324650 // 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.
2462424655
2462524656 var onResolution = function() {
2462624657 ensureRootIsScheduled(root, now());
0 commit comments