@@ -51,6 +51,7 @@ import {
51
51
markRootMutableRead ,
52
52
} from './ReactFiberLane.new' ;
53
53
import {
54
+ DiscreteEventPriority ,
54
55
ContinuousEventPriority ,
55
56
getCurrentUpdatePriority ,
56
57
setCurrentUpdatePriority ,
@@ -136,6 +137,7 @@ export type UpdateQueue<S, A> = {|
136
137
137
138
let didWarnAboutMismatchedHooksForComponent ;
138
139
let didWarnAboutUseOpaqueIdentifier ;
140
+ let didWarnUncachedGetSnapshot ;
139
141
if ( __DEV__ ) {
140
142
didWarnAboutUseOpaqueIdentifier = { } ;
141
143
didWarnAboutMismatchedHooksForComponent = new Set ( ) ;
@@ -1246,14 +1248,127 @@ function mountSyncExternalStore<T>(
1246
1248
subscribe: (() => void ) => ( ) => void ,
1247
1249
getSnapshot : ( ) => T ,
1248
1250
) : T {
1249
- throw new Error ( 'Not yet implemented' ) ;
1251
+ const hook = mountWorkInProgressHook ( ) ;
1252
+ return useSyncExternalStore ( hook , subscribe , getSnapshot ) ;
1250
1253
}
1251
1254
1252
1255
function updateSyncExternalStore< T > (
1253
1256
subscribe: (() => void ) => ( ) => void ,
1254
1257
getSnapshot : ( ) => T ,
1255
1258
) : T {
1256
- throw new Error ( 'Not yet implemented' ) ;
1259
+ const hook = updateWorkInProgressHook ( ) ;
1260
+ return useSyncExternalStore ( hook , subscribe , getSnapshot ) ;
1261
+ }
1262
+
1263
+ function useSyncExternalStore< T > (
1264
+ hook: Hook,
1265
+ subscribe: (() => void ) => ( ) => void ,
1266
+ getSnapshot : ( ) => T ,
1267
+ ) : T {
1268
+ // TODO: This is a copy-paste of the userspace shim. We can improve the
1269
+ // built-in implementation using lower-level APIs. We also intend to move
1270
+ // the tearing checks to an earlier, pre-commit phase so that the layout
1271
+ // effects always observe a consistent tree.
1272
+
1273
+ const dispatcher = ReactCurrentDispatcher . current ;
1274
+
1275
+ // Read the current snapshot from the store on every render. Again, this
1276
+ // breaks the rules of React, and only works here because of specific
1277
+ // implementation details, most importantly that updates are
1278
+ // always synchronous.
1279
+ const value = getSnapshot ( ) ;
1280
+ if ( __DEV__ ) {
1281
+ if ( ! didWarnUncachedGetSnapshot ) {
1282
+ if ( value !== getSnapshot ( ) ) {
1283
+ console . error (
1284
+ 'The result of getSnapshot should be cached to avoid an infinite loop' ,
1285
+ ) ;
1286
+ didWarnUncachedGetSnapshot = true ;
1287
+ }
1288
+ }
1289
+ }
1290
+
1291
+ // Because updates are synchronous, we don't queue them. Instead we force a
1292
+ // re-render whenever the subscribed state changes by updating an some
1293
+ // arbitrary useState hook. Then, during render, we call getSnapshot to read
1294
+ // the current value.
1295
+ //
1296
+ // Because we don't actually use the state returned by the useState hook, we
1297
+ // can save a bit of memory by storing other stuff in that slot.
1298
+ //
1299
+ // To implement the early bailout, we need to track some things on a mutable
1300
+ // object. Usually, we would put that in a useRef hook, but we can stash it in
1301
+ // our useState hook instead.
1302
+ //
1303
+ // To force a re-render, we call forceUpdate({inst}). That works because the
1304
+ // new object always fails an equality check.
1305
+ const [ { inst} , forceUpdate ] = dispatcher . useState ( {
1306
+ inst : { value, getSnapshot} ,
1307
+ } ) ;
1308
+
1309
+ // Track the latest getSnapshot function with a ref. This needs to be updated
1310
+ // in the layout phase so we can access it during the tearing check that
1311
+ // happens on subscribe.
1312
+ // TODO: Circumvent SSR warning
1313
+ dispatcher . useLayoutEffect ( ( ) => {
1314
+ inst . value = value ;
1315
+ inst . getSnapshot = getSnapshot ;
1316
+
1317
+ // Whenever getSnapshot or subscribe changes, we need to check in the
1318
+ // commit phase if there was an interleaved mutation. In concurrent mode
1319
+ // this can happen all the time, but even in synchronous mode, an earlier
1320
+ // effect may have mutated the store.
1321
+ if ( checkIfSnapshotChanged ( inst ) ) {
1322
+ // Force a re-render.
1323
+ const prevTransition = ReactCurrentBatchConfig . transition ;
1324
+ const prevPriority = getCurrentUpdatePriority ( ) ;
1325
+ ReactCurrentBatchConfig . transition = 0 ;
1326
+ setCurrentUpdatePriority ( DiscreteEventPriority ) ;
1327
+ forceUpdate ( { inst} ) ;
1328
+ setCurrentUpdatePriority ( prevPriority ) ;
1329
+ ReactCurrentBatchConfig . transition = prevTransition ;
1330
+ }
1331
+ } , [ subscribe , value , getSnapshot ] ) ;
1332
+
1333
+ dispatcher . useEffect ( ( ) => {
1334
+ const handleStoreChange = ( ) => {
1335
+ // TODO: Because there is no cross-renderer API for batching updates, it's
1336
+ // up to the consumer of this library to wrap their subscription event
1337
+ // with unstable_batchedUpdates. Should we try to detect when this isn't
1338
+ // the case and print a warning in development?
1339
+
1340
+ // The store changed. Check if the snapshot changed since the last time we
1341
+ // read from the store.
1342
+ if ( checkIfSnapshotChanged ( inst ) ) {
1343
+ // Force a re-render.
1344
+ const prevTransition = ReactCurrentBatchConfig . transition ;
1345
+ const prevPriority = getCurrentUpdatePriority ( ) ;
1346
+ ReactCurrentBatchConfig . transition = 0 ;
1347
+ setCurrentUpdatePriority ( DiscreteEventPriority ) ;
1348
+ forceUpdate ( { inst} ) ;
1349
+ setCurrentUpdatePriority ( prevPriority ) ;
1350
+ ReactCurrentBatchConfig . transition = prevTransition ;
1351
+ }
1352
+ } ;
1353
+ // Check for changes right before subscribing. Subsequent changes will be
1354
+ // detected in the subscription handler.
1355
+ handleStoreChange ( ) ;
1356
+ // Subscribe to the store and return a clean-up function.
1357
+ return subscribe ( handleStoreChange ) ;
1358
+ } , [ subscribe ] ) ;
1359
+
1360
+ return value ;
1361
+ }
1362
+
1363
+ function checkIfSnapshotChanged(inst) {
1364
+ const latestGetSnapshot = inst . getSnapshot ;
1365
+ const prevValue = inst . value ;
1366
+ try {
1367
+ const nextValue = latestGetSnapshot ( ) ;
1368
+ return ! is ( prevValue , nextValue ) ;
1369
+ } catch ( error ) {
1370
+ return true ;
1371
+ }
1257
1372
}
1258
1373
1259
1374
function mountState< S > (
0 commit comments