@@ -66,7 +66,7 @@ import {
66
66
} from './ReactChildFiber' ;
67
67
import { processUpdateQueue } from './ReactUpdateQueue' ;
68
68
import { NoWork , Never } from './ReactFiberExpirationTime' ;
69
- import { ConcurrentMode , StrictMode } from './ReactTypeOfMode' ;
69
+ import { ConcurrentMode , StrictMode , NoContext } from './ReactTypeOfMode' ;
70
70
import {
71
71
shouldSetTextContent ,
72
72
shouldDeprioritizeSubtree ,
@@ -1071,31 +1071,21 @@ function updateSuspenseComponent(
1071
1071
// We should attempt to render the primary children unless this boundary
1072
1072
// already suspended during this render (`alreadyCaptured` is true).
1073
1073
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 ;
1076
1080
} 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 ;
1097
1088
}
1098
- const nextDidTimeout = nextState !== null && nextState . didTimeout ;
1099
1089
1100
1090
// This next part is a bit confusing. If the children timeout, we switch to
1101
1091
// showing the fallback children in place of the "primary" children.
@@ -1140,6 +1130,22 @@ function updateSuspenseComponent(
1140
1130
NoWork ,
1141
1131
null ,
1142
1132
) ;
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
+
1143
1149
const fallbackChildFragment = createFiberFromFragment (
1144
1150
nextFallbackChildren ,
1145
1151
mode ,
@@ -1166,7 +1172,7 @@ function updateSuspenseComponent(
1166
1172
// This is an update. This branch is more complicated because we need to
1167
1173
// ensure the state of the primary children is preserved.
1168
1174
const prevState = current . memoizedState ;
1169
- const prevDidTimeout = prevState !== null && prevState . didTimeout ;
1175
+ const prevDidTimeout = prevState !== null ;
1170
1176
if ( prevDidTimeout ) {
1171
1177
// The current tree already timed out. That means each child set is
1172
1178
// wrapped in a fragment fiber.
@@ -1182,6 +1188,24 @@ function updateSuspenseComponent(
1182
1188
NoWork ,
1183
1189
) ;
1184
1190
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
+
1185
1209
// Clone the fallback child fragment, too. These we'll continue
1186
1210
// working on.
1187
1211
const fallbackChildFragment = ( primaryChildFragment . sibling = createWorkInProgress (
@@ -1237,6 +1261,22 @@ function updateSuspenseComponent(
1237
1261
primaryChildFragment . effectTag |= Placement ;
1238
1262
primaryChildFragment . child = currentPrimaryChild ;
1239
1263
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
+
1240
1280
// Create a fragment from the fallback children, too.
1241
1281
const fallbackChildFragment = ( primaryChildFragment . sibling = createFiberFromFragment (
1242
1282
nextFallbackChildren ,
@@ -1270,6 +1310,49 @@ function updateSuspenseComponent(
1270
1310
return next ;
1271
1311
}
1272
1312
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
+
1273
1356
function updatePortalComponent (
1274
1357
current : Fiber | null ,
1275
1358
workInProgress : Fiber ,
@@ -1426,25 +1509,6 @@ function updateContextConsumer(
1426
1509
return workInProgress . child ;
1427
1510
}
1428
1511
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
-
1448
1512
function bailoutOnAlreadyFinishedWork (
1449
1513
current : Fiber | null ,
1450
1514
workInProgress : Fiber ,
@@ -1528,7 +1592,7 @@ function beginWork(
1528
1592
break ;
1529
1593
case SuspenseComponent : {
1530
1594
const state : SuspenseState | null = workInProgress . memoizedState ;
1531
- const didTimeout = state !== null && state . didTimeout ;
1595
+ const didTimeout = state !== null ;
1532
1596
if ( didTimeout ) {
1533
1597
// If this boundary is currently timed out, we need to decide
1534
1598
// whether to retry the primary children, or to skip over it and
0 commit comments