@@ -43,6 +43,7 @@ import {
4343 DidCapture ,
4444 Update ,
4545 Ref ,
46+ Incomplete ,
4647} from 'shared/ReactSideEffectTags' ;
4748import ReactSharedInternals from 'shared/ReactSharedInternals' ;
4849import {
@@ -66,7 +67,7 @@ import {
6667} from './ReactChildFiber' ;
6768import { processUpdateQueue } from './ReactUpdateQueue' ;
6869import { NoWork , Never } from './ReactFiberExpirationTime' ;
69- import { ConcurrentMode , StrictMode } from './ReactTypeOfMode' ;
70+ import { ConcurrentMode , StrictMode , NoContext } from './ReactTypeOfMode' ;
7071import {
7172 shouldSetTextContent ,
7273 shouldDeprioritizeSubtree ,
@@ -1071,31 +1072,21 @@ function updateSuspenseComponent(
10711072 // We should attempt to render the primary children unless this boundary
10721073 // already suspended during this render (`alreadyCaptured` is true).
10731074 let nextState : SuspenseState | null = workInProgress . memoizedState ;
1074- if ( nextState === null ) {
1075- // An empty suspense state means this boundary has not yet timed out.
1075+
1076+ let nextDidTimeout ;
1077+ if ( ( workInProgress . effectTag & DidCapture ) === NoEffect ) {
1078+ // This is the first attempt.
1079+ nextState = null ;
1080+ nextDidTimeout = false ;
10761081 } else {
1077- if ( ! nextState . alreadyCaptured ) {
1078- // Since we haven't already suspended during this commit, clear the
1079- // existing suspense state. We'll try rendering again.
1080- nextState = null ;
1081- } else {
1082- // Something in this boundary's subtree already suspended. Switch to
1083- // rendering the fallback children. Set `alreadyCaptured` to true.
1084- if ( current !== null && nextState === current . memoizedState ) {
1085- // Create a new suspense state to avoid mutating the current tree's.
1086- nextState = {
1087- alreadyCaptured : true ,
1088- didTimeout : true ,
1089- timedOutAt : nextState . timedOutAt ,
1090- } ;
1091- } else {
1092- // Already have a clone, so it's safe to mutate.
1093- nextState . alreadyCaptured = true ;
1094- nextState . didTimeout = true ;
1095- }
1096- }
1082+ // Something in this boundary's subtree already suspended. Switch to
1083+ // rendering the fallback children.
1084+ nextState = {
1085+ timedOutAt : nextState !== null ? nextState . timedOutAt : NoWork ,
1086+ } ;
1087+ nextDidTimeout = true ;
1088+ workInProgress . effectTag &= ~ DidCapture ;
10971089 }
1098- const nextDidTimeout = nextState !== null && nextState . didTimeout ;
10991090
11001091 // This next part is a bit confusing. If the children timeout, we switch to
11011092 // showing the fallback children in place of the "primary" children.
@@ -1140,6 +1131,22 @@ function updateSuspenseComponent(
11401131 NoWork ,
11411132 null ,
11421133 ) ;
1134+
1135+ if ( ( workInProgress . mode & ConcurrentMode ) === NoContext ) {
1136+ // Outside of concurrent mode, we commit the effects from the
1137+ // partially completed, timed-out tree, too.
1138+ const progressedState : SuspenseState = workInProgress . memoizedState ;
1139+ const progressedPrimaryChild : Fiber | null =
1140+ progressedState !== null
1141+ ? ( workInProgress . child : any ) . child
1142+ : ( workInProgress . child : any ) ;
1143+ reuseProgressedPrimaryChild (
1144+ workInProgress ,
1145+ primaryChildFragment ,
1146+ progressedPrimaryChild ,
1147+ ) ;
1148+ }
1149+
11431150 const fallbackChildFragment = createFiberFromFragment (
11441151 nextFallbackChildren ,
11451152 mode ,
@@ -1166,7 +1173,7 @@ function updateSuspenseComponent(
11661173 // This is an update. This branch is more complicated because we need to
11671174 // ensure the state of the primary children is preserved.
11681175 const prevState = current . memoizedState ;
1169- const prevDidTimeout = prevState !== null && prevState . didTimeout ;
1176+ const prevDidTimeout = prevState !== null ;
11701177 if ( prevDidTimeout ) {
11711178 // The current tree already timed out. That means each child set is
11721179 // wrapped in a fragment fiber.
@@ -1182,6 +1189,24 @@ function updateSuspenseComponent(
11821189 NoWork ,
11831190 ) ;
11841191 primaryChildFragment . effectTag |= Placement ;
1192+
1193+ if ( ( workInProgress . mode & ConcurrentMode ) === NoContext ) {
1194+ // Outside of concurrent mode, we commit the effects from the
1195+ // partially completed, timed-out tree, too.
1196+ const progressedState : SuspenseState = workInProgress . memoizedState ;
1197+ const progressedPrimaryChild : Fiber | null =
1198+ progressedState !== null
1199+ ? ( workInProgress . child : any ) . child
1200+ : ( workInProgress . child : any ) ;
1201+ if ( progressedPrimaryChild !== currentPrimaryChildFragment . child ) {
1202+ reuseProgressedPrimaryChild (
1203+ workInProgress ,
1204+ primaryChildFragment ,
1205+ progressedPrimaryChild ,
1206+ ) ;
1207+ }
1208+ }
1209+
11851210 // Clone the fallback child fragment, too. These we'll continue
11861211 // working on.
11871212 const fallbackChildFragment = ( primaryChildFragment . sibling = createWorkInProgress (
@@ -1237,6 +1262,22 @@ function updateSuspenseComponent(
12371262 primaryChildFragment . effectTag |= Placement ;
12381263 primaryChildFragment . child = currentPrimaryChild ;
12391264 currentPrimaryChild . return = primaryChildFragment ;
1265+
1266+ if ( ( workInProgress . mode & ConcurrentMode ) === NoContext ) {
1267+ // Outside of concurrent mode, we commit the effects from the
1268+ // partially completed, timed-out tree, too.
1269+ const progressedState : SuspenseState = workInProgress . memoizedState ;
1270+ const progressedPrimaryChild : Fiber | null =
1271+ progressedState !== null
1272+ ? ( workInProgress . child : any ) . child
1273+ : ( workInProgress . child : any ) ;
1274+ reuseProgressedPrimaryChild (
1275+ workInProgress ,
1276+ primaryChildFragment ,
1277+ progressedPrimaryChild ,
1278+ ) ;
1279+ }
1280+
12401281 // Create a fragment from the fallback children, too.
12411282 const fallbackChildFragment = ( primaryChildFragment . sibling = createFiberFromFragment (
12421283 nextFallbackChildren ,
@@ -1270,6 +1311,46 @@ function updateSuspenseComponent(
12701311 return next ;
12711312}
12721313
1314+ function reuseProgressedPrimaryChild (
1315+ workInProgress : Fiber ,
1316+ primaryChildFragment : Fiber ,
1317+ progressedChild : Fiber | null ,
1318+ ) {
1319+ let child = ( primaryChildFragment . child = progressedChild ) ;
1320+ while ( child !== null ) {
1321+ if ( ( child . effectTag & Incomplete ) === NoEffect ) {
1322+ // Ensure that the first and last effect of the parent corresponds
1323+ // to the children's first and last effect.
1324+ if ( primaryChildFragment . firstEffect === null ) {
1325+ primaryChildFragment . firstEffect = child . firstEffect ;
1326+ }
1327+ if ( child . lastEffect !== null ) {
1328+ if ( primaryChildFragment . lastEffect !== null ) {
1329+ primaryChildFragment . lastEffect . nextEffect = child . firstEffect ;
1330+ }
1331+ primaryChildFragment . lastEffect = child . lastEffect ;
1332+ }
1333+
1334+ // Append all the effects of the subtree and this fiber onto the effect
1335+ // list of the parent. The completion order of the children affects the
1336+ // side-effect order.
1337+ if ( child . effectTag > PerformedWork ) {
1338+ if ( primaryChildFragment . lastEffect !== null ) {
1339+ primaryChildFragment . lastEffect . nextEffect = child ;
1340+ } else {
1341+ primaryChildFragment . firstEffect = child ;
1342+ }
1343+ primaryChildFragment . lastEffect = child ;
1344+ }
1345+ }
1346+ child . return = primaryChildFragment ;
1347+ child = child . sibling ;
1348+ }
1349+
1350+ workInProgress . firstEffect = primaryChildFragment . firstEffect ;
1351+ workInProgress . lastEffect = primaryChildFragment . lastEffect ;
1352+ }
1353+
12731354function updatePortalComponent (
12741355 current : Fiber | null ,
12751356 workInProgress : Fiber ,
@@ -1426,25 +1507,6 @@ function updateContextConsumer(
14261507 return workInProgress . child ;
14271508}
14281509
1429- /*
1430- function reuseChildrenEffects(returnFiber : Fiber, firstChild : Fiber) {
1431- let child = firstChild;
1432- do {
1433- // Ensure that the first and last effect of the parent corresponds
1434- // to the children's first and last effect.
1435- if (!returnFiber.firstEffect) {
1436- returnFiber.firstEffect = child.firstEffect;
1437- }
1438- if (child.lastEffect) {
1439- if (returnFiber.lastEffect) {
1440- returnFiber.lastEffect.nextEffect = child.firstEffect;
1441- }
1442- returnFiber.lastEffect = child.lastEffect;
1443- }
1444- } while (child = child.sibling);
1445- }
1446- */
1447-
14481510function bailoutOnAlreadyFinishedWork (
14491511 current : Fiber | null ,
14501512 workInProgress : Fiber ,
@@ -1528,7 +1590,7 @@ function beginWork(
15281590 break ;
15291591 case SuspenseComponent : {
15301592 const state : SuspenseState | null = workInProgress . memoizedState ;
1531- const didTimeout = state !== null && state . didTimeout ;
1593+ const didTimeout = state !== null ;
15321594 if ( didTimeout ) {
15331595 // If this boundary is currently timed out, we need to decide
15341596 // whether to retry the primary children, or to skip over it and
0 commit comments