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