@@ -66,7 +66,7 @@ import {
6666} from './ReactChildFiber' ;
6767import { processUpdateQueue } from './ReactUpdateQueue' ;
6868import { NoWork , Never } from './ReactFiberExpirationTime' ;
69- import { ConcurrentMode , StrictMode } from './ReactTypeOfMode' ;
69+ import { ConcurrentMode , StrictMode , NoContext } from './ReactTypeOfMode' ;
7070import {
7171 shouldSetTextContent ,
7272 shouldDeprioritizeSubtree ,
@@ -1071,31 +1071,21 @@ function updateSuspenseComponent(
10711071 // We should attempt to render the primary children unless this boundary
10721072 // already suspended during this render (`alreadyCaptured` is true).
10731073 let nextState : SuspenseState | null = workInProgress . memoizedState ;
1074- if ( nextState === null ) {
1075- // An empty suspense state means this boundary has not yet timed out.
1074+
1075+ let nextDidTimeout ;
1076+ if ( ( workInProgress . effectTag & DidCapture ) === NoEffect ) {
1077+ // This is the first attempt.
1078+ nextState = null ;
1079+ nextDidTimeout = false ;
10761080 } 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- }
1081+ // Something in this boundary's subtree already suspended. Switch to
1082+ // rendering the fallback children.
1083+ nextState = {
1084+ timedOutAt : nextState !== null ? nextState . timedOutAt : NoWork ,
1085+ } ;
1086+ nextDidTimeout = true ;
1087+ workInProgress . effectTag &= ~ DidCapture ;
10971088 }
1098- const nextDidTimeout = nextState !== null && nextState . didTimeout ;
10991089
11001090 // This next part is a bit confusing. If the children timeout, we switch to
11011091 // showing the fallback children in place of the "primary" children.
@@ -1140,6 +1130,22 @@ function updateSuspenseComponent(
11401130 NoWork ,
11411131 null ,
11421132 ) ;
1133+
1134+ if ( ( workInProgress . mode & ConcurrentMode ) === NoContext ) {
1135+ // Outside of concurrent mode, we commit the effects from the
1136+ // partially completed, timed-out tree, too.
1137+ const progressedState : SuspenseState = workInProgress . memoizedState ;
1138+ const progressedPrimaryChild : Fiber | null =
1139+ progressedState !== null
1140+ ? ( workInProgress . child : any ) . child
1141+ : ( workInProgress . child : any ) ;
1142+ reuseProgressedPrimaryChild (
1143+ workInProgress ,
1144+ primaryChildFragment ,
1145+ progressedPrimaryChild ,
1146+ ) ;
1147+ }
1148+
11431149 const fallbackChildFragment = createFiberFromFragment (
11441150 nextFallbackChildren ,
11451151 mode ,
@@ -1166,7 +1172,7 @@ function updateSuspenseComponent(
11661172 // This is an update. This branch is more complicated because we need to
11671173 // ensure the state of the primary children is preserved.
11681174 const prevState = current . memoizedState ;
1169- const prevDidTimeout = prevState !== null && prevState . didTimeout ;
1175+ const prevDidTimeout = prevState !== null ;
11701176 if ( prevDidTimeout ) {
11711177 // The current tree already timed out. That means each child set is
11721178 // wrapped in a fragment fiber.
@@ -1182,6 +1188,24 @@ function updateSuspenseComponent(
11821188 NoWork ,
11831189 ) ;
11841190 primaryChildFragment . effectTag |= Placement ;
1191+
1192+ if ( ( workInProgress . mode & ConcurrentMode ) === NoContext ) {
1193+ // Outside of concurrent mode, we commit the effects from the
1194+ // partially completed, timed-out tree, too.
1195+ const progressedState : SuspenseState = workInProgress . memoizedState ;
1196+ const progressedPrimaryChild : Fiber | null =
1197+ progressedState !== null
1198+ ? ( workInProgress . child : any ) . child
1199+ : ( workInProgress . child : any ) ;
1200+ if ( progressedPrimaryChild !== currentPrimaryChildFragment . child ) {
1201+ reuseProgressedPrimaryChild (
1202+ workInProgress ,
1203+ primaryChildFragment ,
1204+ progressedPrimaryChild ,
1205+ ) ;
1206+ }
1207+ }
1208+
11851209 // Clone the fallback child fragment, too. These we'll continue
11861210 // working on.
11871211 const fallbackChildFragment = ( primaryChildFragment . sibling = createWorkInProgress (
@@ -1237,6 +1261,22 @@ function updateSuspenseComponent(
12371261 primaryChildFragment . effectTag |= Placement ;
12381262 primaryChildFragment . child = currentPrimaryChild ;
12391263 currentPrimaryChild . return = primaryChildFragment ;
1264+
1265+ if ( ( workInProgress . mode & ConcurrentMode ) === NoContext ) {
1266+ // Outside of concurrent mode, we commit the effects from the
1267+ // partially completed, timed-out tree, too.
1268+ const progressedState : SuspenseState = workInProgress . memoizedState ;
1269+ const progressedPrimaryChild : Fiber | null =
1270+ progressedState !== null
1271+ ? ( workInProgress . child : any ) . child
1272+ : ( workInProgress . child : any ) ;
1273+ reuseProgressedPrimaryChild (
1274+ workInProgress ,
1275+ primaryChildFragment ,
1276+ progressedPrimaryChild ,
1277+ ) ;
1278+ }
1279+
12401280 // Create a fragment from the fallback children, too.
12411281 const fallbackChildFragment = ( primaryChildFragment . sibling = createFiberFromFragment (
12421282 nextFallbackChildren ,
@@ -1270,6 +1310,49 @@ function updateSuspenseComponent(
12701310 return next ;
12711311}
12721312
1313+ function reuseProgressedPrimaryChild (
1314+ workInProgress : Fiber ,
1315+ primaryChildFragment : Fiber ,
1316+ progressedChild : Fiber | null ,
1317+ ) {
1318+ // This function is only called outside concurrent mode. Usually, if a work-
1319+ // in-progress primary tree suspends, we throw it out and revert back to
1320+ // current. Outside concurrent mode, though, we commit the suspended work-in-
1321+ // progress, even though it didn't complete. This function reuses the children
1322+ // and transfers the effects.
1323+ let child = ( primaryChildFragment . child = progressedChild ) ;
1324+ while ( child !== null ) {
1325+ // Ensure that the first and last effect of the parent corresponds
1326+ // to the children's first and last effect.
1327+ if ( primaryChildFragment . firstEffect === null ) {
1328+ primaryChildFragment . firstEffect = child . firstEffect ;
1329+ }
1330+ if ( child . lastEffect !== null ) {
1331+ if ( primaryChildFragment . lastEffect !== null ) {
1332+ primaryChildFragment . lastEffect . nextEffect = child . firstEffect ;
1333+ }
1334+ primaryChildFragment . lastEffect = child . lastEffect ;
1335+ }
1336+
1337+ // Append all the effects of the subtree and this fiber onto the effect
1338+ // list of the parent. The completion order of the children affects the
1339+ // side-effect order.
1340+ if ( child . effectTag > PerformedWork ) {
1341+ if ( primaryChildFragment . lastEffect !== null ) {
1342+ primaryChildFragment . lastEffect . nextEffect = child ;
1343+ } else {
1344+ primaryChildFragment . firstEffect = child ;
1345+ }
1346+ primaryChildFragment . lastEffect = child ;
1347+ }
1348+ child . return = primaryChildFragment ;
1349+ child = child . sibling ;
1350+ }
1351+
1352+ workInProgress . firstEffect = primaryChildFragment . firstEffect ;
1353+ workInProgress . lastEffect = primaryChildFragment . lastEffect ;
1354+ }
1355+
12731356function updatePortalComponent (
12741357 current : Fiber | null ,
12751358 workInProgress : Fiber ,
@@ -1426,25 +1509,6 @@ function updateContextConsumer(
14261509 return workInProgress . child ;
14271510}
14281511
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-
14481512function bailoutOnAlreadyFinishedWork (
14491513 current : Fiber | null ,
14501514 workInProgress : Fiber ,
@@ -1528,7 +1592,7 @@ function beginWork(
15281592 break ;
15291593 case SuspenseComponent : {
15301594 const state : SuspenseState | null = workInProgress . memoizedState ;
1531- const didTimeout = state !== null && state . didTimeout ;
1595+ const didTimeout = state !== null ;
15321596 if ( didTimeout ) {
15331597 // If this boundary is currently timed out, we need to decide
15341598 // whether to retry the primary children, or to skip over it and
0 commit comments