@@ -44,6 +44,7 @@ import {
44
44
SyncLane ,
45
45
NoLanes ,
46
46
isSubsetOfLanes ,
47
+ includesBlockingLane ,
47
48
mergeLanes ,
48
49
removeLanes ,
49
50
intersectLanes ,
@@ -68,6 +69,7 @@ import {
68
69
PassiveStatic as PassiveStaticEffect ,
69
70
StaticMask as StaticMaskEffect ,
70
71
Update as UpdateEffect ,
72
+ StoreConsistency ,
71
73
} from './ReactFiberFlags' ;
72
74
import {
73
75
HasEffect as HookHasEffect ,
@@ -166,7 +168,15 @@ type StoreInstance<T> = {|
166
168
getSnapshot : ( ) => T ,
167
169
| } ;
168
170
169
- export type FunctionComponentUpdateQueue = { | lastEffect : Effect | null | } ;
171
+ type StoreConsistencyCheck < T > = { |
172
+ value : T ,
173
+ getSnapshot : ( ) => T ,
174
+ | } ;
175
+
176
+ export type FunctionComponentUpdateQueue = { |
177
+ lastEffect : Effect | null ,
178
+ stores : Array < StoreConsistencyCheck < any >> | null ,
179
+ | } ;
170
180
171
181
type BasicStateAction < S > = ( S => S ) | S ;
172
182
@@ -689,6 +699,7 @@ function updateWorkInProgressHook(): Hook {
689
699
function createFunctionComponentUpdateQueue ( ) : FunctionComponentUpdateQueue {
690
700
return {
691
701
lastEffect : null ,
702
+ stores : null ,
692
703
} ;
693
704
}
694
705
@@ -1289,17 +1300,25 @@ function mountSyncExternalStore<T>(
1289
1300
// don't need to set a static flag, either.
1290
1301
// TODO: We can move this to the passive phase once we add a pre-commit
1291
1302
// consistency check. See the next comment.
1292
- fiber . flags |= UpdateEffect ;
1303
+ fiber . flags |= PassiveEffect ;
1293
1304
pushEffect (
1294
- HookHasEffect | HookLayout ,
1305
+ HookHasEffect | HookPassive ,
1295
1306
updateStoreInstance . bind ( null , fiber , inst , nextSnapshot , getSnapshot ) ,
1296
1307
undefined ,
1297
1308
null ,
1298
1309
) ;
1299
- // TODO: Unless this is a synchronous render, schedule a consistency check.
1300
- // Right before committing, we will walk the tree and check if any of the
1301
- // stores were mutated.
1302
- // pushConsistencyCheck(inst, getSnapshot, nextSnapshot);
1310
+
1311
+ // Unless we're rendering a blocking lane, schedule a consistency check. Right
1312
+ // before committing, we will walk the tree and check if any of the stores
1313
+ // were mutated.
1314
+ const root : FiberRoot | null = getWorkInProgressRoot ( ) ;
1315
+ invariant (
1316
+ root !== null ,
1317
+ 'Expected a work-in-progress root. This is a bug in React. Please file an issue.' ,
1318
+ ) ;
1319
+ if ( ! includesBlockingLane ( root , renderLanes ) ) {
1320
+ pushStoreConsistencyCheck ( fiber , getSnapshot , nextSnapshot ) ;
1321
+ }
1303
1322
1304
1323
return nextSnapshot ;
1305
1324
}
@@ -1348,36 +1367,69 @@ function updateSyncExternalStore<T>(
1348
1367
( workInProgressHook !== null &&
1349
1368
workInProgressHook . memoizedState . tag & HookHasEffect )
1350
1369
) {
1351
- // TODO: We can move this to the passive phase once we add a pre-commit
1352
- // consistency check. See the next comment.
1353
- fiber . flags |= UpdateEffect ;
1370
+ fiber . flags |= PassiveEffect ;
1354
1371
pushEffect (
1355
- HookHasEffect | HookLayout ,
1372
+ HookHasEffect | HookPassive ,
1356
1373
updateStoreInstance . bind ( null , fiber , inst , nextSnapshot , getSnapshot ) ,
1357
1374
undefined ,
1358
1375
null ,
1359
1376
) ;
1360
1377
1361
- // TODO: Unless this is a synchronous render , schedule a consistency check.
1378
+ // Unless we're rendering a blocking lane , schedule a consistency check.
1362
1379
// Right before committing, we will walk the tree and check if any of the
1363
1380
// stores were mutated.
1364
- // pushConsistencyCheck(inst, getSnapshot, nextSnapshot);
1381
+ const root : FiberRoot | null = getWorkInProgressRoot ( ) ;
1382
+ invariant (
1383
+ root !== null ,
1384
+ 'Expected a work-in-progress root. This is a bug in React. Please file an issue.' ,
1385
+ ) ;
1386
+ if ( ! includesBlockingLane ( root , renderLanes ) ) {
1387
+ pushStoreConsistencyCheck ( fiber , getSnapshot , nextSnapshot ) ;
1388
+ }
1365
1389
}
1366
1390
1367
1391
return nextSnapshot;
1368
1392
}
1369
1393
1394
+ function pushStoreConsistencyCheck < T > (
1395
+ fiber: Fiber,
1396
+ getSnapshot: () => T ,
1397
+ renderedSnapshot : T ,
1398
+ ) {
1399
+ fiber . flags |= StoreConsistency ;
1400
+ const check : StoreConsistencyCheck < T > = {
1401
+ getSnapshot,
1402
+ value : renderedSnapshot ,
1403
+ } ;
1404
+ let componentUpdateQueue : null | FunctionComponentUpdateQueue = ( currentlyRenderingFiber . updateQueue : any ) ;
1405
+ if ( componentUpdateQueue === null ) {
1406
+ componentUpdateQueue = createFunctionComponentUpdateQueue ( ) ;
1407
+ currentlyRenderingFiber . updateQueue = ( componentUpdateQueue : any ) ;
1408
+ componentUpdateQueue . stores = [ check ] ;
1409
+ } else {
1410
+ const stores = componentUpdateQueue . stores ;
1411
+ if ( stores === null ) {
1412
+ componentUpdateQueue . stores = [ check ] ;
1413
+ } else {
1414
+ stores . push ( check ) ;
1415
+ }
1416
+ }
1417
+ }
1418
+
1370
1419
function updateStoreInstance < T > (
1371
1420
fiber: Fiber,
1372
1421
inst: StoreInstance< T > ,
1373
1422
nextSnapshot: T,
1374
1423
getSnapshot: () => T ,
1375
1424
) {
1425
+ // These are updated in the passive phase
1376
1426
inst . value = nextSnapshot ;
1377
1427
inst . getSnapshot = getSnapshot ;
1378
1428
1379
- // TODO: Move the tearing checks to an earlier, pre-commit phase so that the
1380
- // layout effects always observe a consistent tree.
1429
+ // Something may have been mutated in between render and commit. This could
1430
+ // have been in an event that fired before the passive effects, or it could
1431
+ // have been in a layout effect. In that case, we would have used the old
1432
+ // snapsho and getSnapshot values to bail out. We need to check one more time.
1381
1433
if ( checkIfSnapshotChanged ( inst ) ) {
1382
1434
// Force a re-render.
1383
1435
forceStoreRerender ( fiber ) ;
@@ -1393,11 +1445,6 @@ function subscribeToStore(fiber, inst, subscribe) {
1393
1445
forceStoreRerender ( fiber ) ;
1394
1446
}
1395
1447
} ;
1396
- // Check for changes right before subscribing. Subsequent changes will be
1397
- // detected in the subscription handler.
1398
- // TODO: Once updateStoreInstance is moved to the passive phase, we can rely
1399
- // on that check instead of checking again here.
1400
- handleStoreChange ( ) ;
1401
1448
// Subscribe to the store and return a clean-up function.
1402
1449
return subscribe ( handleStoreChange ) ;
1403
1450
}
0 commit comments