@@ -12,6 +12,7 @@ import type {Fiber} from './ReactFiber';
1212import type { FiberRoot } from './ReactFiberRoot' ;
1313import type { ExpirationTime } from './ReactFiberExpirationTime' ;
1414import type { SuspenseState } from './ReactFiberSuspenseComponent' ;
15+ import type { SuspenseContext } from './ReactFiberSuspenseContext' ;
1516
1617import checkPropTypes from 'prop-types/checkPropTypes' ;
1718
@@ -104,6 +105,17 @@ import {
104105 pushHostContextForEventComponent ,
105106 pushHostContextForEventTarget ,
106107} from './ReactFiberHostContext' ;
108+ import {
109+ suspenseStackCursor ,
110+ pushSuspenseContext ,
111+ popSuspenseContext ,
112+ DefaultShallowSuspenseContext ,
113+ InvisibleParentSuspenseContext ,
114+ ForceSuspenseFallback ,
115+ hasSuspenseContext ,
116+ setShallowSuspenseContext ,
117+ addSubtreeSuspenseContext ,
118+ } from './ReactFiberSuspenseContext' ;
107119import {
108120 pushProvider ,
109121 propagateContextChange ,
@@ -1394,32 +1406,65 @@ function updateSuspenseComponent(
13941406 const mode = workInProgress . mode ;
13951407 const nextProps = workInProgress . pendingProps ;
13961408
1409+ // This is used by DevTools to force a boundary to suspend.
13971410 if ( __DEV__ ) {
13981411 if ( shouldSuspend ( workInProgress ) ) {
13991412 workInProgress . effectTag |= DidCapture ;
14001413 }
14011414 }
14021415
1403- // We should attempt to render the primary children unless this boundary
1404- // already suspended during this render (`alreadyCaptured` is true).
1405- let nextState : SuspenseState | null = workInProgress . memoizedState ;
1416+ let suspenseContext : SuspenseContext = suspenseStackCursor . current ;
14061417
1407- let nextDidTimeout ;
1408- if ( ( workInProgress . effectTag & DidCapture ) === NoEffect ) {
1409- // This is the first attempt.
1410- nextState = null ;
1411- nextDidTimeout = false ;
1412- } else {
1418+ let nextState = null ;
1419+ let nextDidTimeout = false ;
1420+
1421+ if (
1422+ ( workInProgress . effectTag & DidCapture ) !== NoEffect ||
1423+ hasSuspenseContext (
1424+ suspenseContext ,
1425+ ( ForceSuspenseFallback : SuspenseContext ) ,
1426+ )
1427+ ) {
1428+ // This either already captured or is a new mount that was forced into its fallback
1429+ // state by a parernt.
1430+ const attemptedState : SuspenseState | null = workInProgress . memoizedState ;
14131431 // Something in this boundary's subtree already suspended. Switch to
14141432 // rendering the fallback children.
14151433 nextState = {
14161434 fallbackExpirationTime :
1417- nextState !== null ? nextState . fallbackExpirationTime : NoWork ,
1435+ attemptedState !== null
1436+ ? attemptedState . fallbackExpirationTime
1437+ : NoWork ,
14181438 } ;
14191439 nextDidTimeout = true ;
14201440 workInProgress . effectTag &= ~ DidCapture ;
1441+ } else {
1442+ // Attempting the main content the main content
1443+ if ( current === null || current . memoizedState !== null ) {
1444+ // This is a new mount or this boundary is already showing a fallback state.
1445+ // Mark this subtree context as having at least one invisible parent that could
1446+ // handle the fallback state.
1447+ // Boundaries without fallbacks or should be avoided are not considered since
1448+ // they cannot handle preferred fallback states.
1449+ if (
1450+ nextProps . fallback !== undefined &&
1451+ nextProps . unstable_avoidThisFallback !== true
1452+ ) {
1453+ suspenseContext = addSubtreeSuspenseContext (
1454+ suspenseContext ,
1455+ InvisibleParentSuspenseContext ,
1456+ ) ;
1457+ }
1458+ }
14211459 }
14221460
1461+ suspenseContext = setShallowSuspenseContext (
1462+ suspenseContext ,
1463+ DefaultShallowSuspenseContext ,
1464+ ) ;
1465+
1466+ pushSuspenseContext ( workInProgress , suspenseContext ) ;
1467+
14231468 if ( __DEV__ ) {
14241469 if ( 'maxDuration' in nextProps ) {
14251470 if ( ! didWarnAboutMaxDuration ) {
@@ -1472,6 +1517,7 @@ function updateSuspenseComponent(
14721517 tryToClaimNextHydratableInstance ( workInProgress ) ;
14731518 // This could've changed the tag if this was a dehydrated suspense component.
14741519 if ( workInProgress . tag === DehydratedSuspenseComponent ) {
1520+ popSuspenseContext ( workInProgress ) ;
14751521 return updateDehydratedSuspenseComponent (
14761522 null ,
14771523 workInProgress ,
@@ -1713,6 +1759,8 @@ function retrySuspenseComponentWithoutHydrating(
17131759 current . nextEffect = null ;
17141760 current . effectTag = Deletion ;
17151761
1762+ popSuspenseContext ( workInProgress ) ;
1763+
17161764 // Upgrade this work in progress to a real Suspense component.
17171765 workInProgress . tag = SuspenseComponent ;
17181766 workInProgress . stateNode = null ;
@@ -1728,6 +1776,13 @@ function updateDehydratedSuspenseComponent(
17281776 workInProgress : Fiber ,
17291777 renderExpirationTime : ExpirationTime ,
17301778) {
1779+ pushSuspenseContext (
1780+ workInProgress ,
1781+ setShallowSuspenseContext (
1782+ suspenseStackCursor . current ,
1783+ DefaultShallowSuspenseContext ,
1784+ ) ,
1785+ ) ;
17311786 const suspenseInstance = ( workInProgress . stateNode : SuspenseInstance ) ;
17321787 if ( current === null ) {
17331788 // During the first pass, we'll bail out and not drill into the children.
@@ -2131,6 +2186,13 @@ function beginWork(
21312186 renderExpirationTime ,
21322187 ) ;
21332188 } else {
2189+ pushSuspenseContext (
2190+ workInProgress ,
2191+ setShallowSuspenseContext (
2192+ suspenseStackCursor . current ,
2193+ DefaultShallowSuspenseContext ,
2194+ ) ,
2195+ ) ;
21342196 // The primary children do not have pending work with sufficient
21352197 // priority. Bailout.
21362198 const child = bailoutOnAlreadyFinishedWork (
@@ -2146,11 +2208,26 @@ function beginWork(
21462208 return null ;
21472209 }
21482210 }
2211+ } else {
2212+ pushSuspenseContext (
2213+ workInProgress ,
2214+ setShallowSuspenseContext (
2215+ suspenseStackCursor . current ,
2216+ DefaultShallowSuspenseContext ,
2217+ ) ,
2218+ ) ;
21492219 }
21502220 break ;
21512221 }
21522222 case DehydratedSuspenseComponent: {
21532223 if ( enableSuspenseServerRenderer ) {
2224+ pushSuspenseContext (
2225+ workInProgress ,
2226+ setShallowSuspenseContext (
2227+ suspenseStackCursor . current ,
2228+ DefaultShallowSuspenseContext ,
2229+ ) ,
2230+ ) ;
21542231 // We know that this component will suspend again because if it has
21552232 // been unsuspended it has committed as a regular Suspense component.
21562233 // If it needs to be retried, it should have work scheduled on it.
0 commit comments