@@ -17,7 +17,7 @@ import type {SuspenseConfig} from './ReactFiberSuspenseConfig';
17
17
import ReactSharedInternals from 'shared/ReactSharedInternals' ;
18
18
19
19
import { NoWork } from './ReactFiberExpirationTime' ;
20
- import { readContext } from './ReactFiberNewContext' ;
20
+ import { readContext , selectFromContext } from './ReactFiberNewContext' ;
21
21
import {
22
22
Update as UpdateEffect ,
23
23
Passive as PassiveEffect ,
@@ -64,6 +64,7 @@ export type Dispatcher = {
64
64
context : ReactContext < T > ,
65
65
observedBits : void | number | boolean ,
66
66
) : T ,
67
+ useContextSelector < T , S > ( context : ReactContext < T > , selector : ( T ) = > S ) : S ,
67
68
useRef < T > ( initialValue : T ) : { current : T } ,
68
69
useEffect (
69
70
create : ( ) = > ( ( ) => void ) | void ,
@@ -602,6 +603,63 @@ function updateWorkInProgressHook(): Hook {
602
603
return workInProgressHook ;
603
604
}
604
605
606
+ function makeSelect < T , S > (
607
+ context : ReactContext < T > ,
608
+ selector : T => S ,
609
+ ) : ( T , ( T ) => S ) => [ S , boolean ] {
610
+ // close over memoized value and selection
611
+ let previousValue , previousSelection;
612
+
613
+ // select function will return a tuple of the selection as well as whether
614
+ // the selection was a new value or not
615
+ return function select ( value : T ) {
616
+ let selection = previousSelection ;
617
+ let isNew = false ;
618
+
619
+ // don't recompute if values are the same
620
+ if ( ! is ( value , previousValue ) ) {
621
+ selection = selector ( value ) ;
622
+ if ( ! is ( selection , previousSelection ) ) {
623
+ // if same we can still consider the selection memoized since the selected values are identical
624
+ isNew = true ;
625
+ }
626
+ }
627
+ previousValue = value ;
628
+ previousSelection = selection ;
629
+ return [ selection , isNew ] ;
630
+ } ;
631
+ }
632
+
633
+ function mountContextSelector < T , S > (
634
+ context : ReactContext < T > ,
635
+ selector : T => S ,
636
+ ) : S {
637
+ const hook = mountWorkInProgressHook ( ) ;
638
+ let select = makeSelect ( context , selector ) ;
639
+ let [ selection ] = selectFromContext ( context , select ) ;
640
+ hook . memoizedState = [ context , selector , select ] ;
641
+ return selection ;
642
+ }
643
+
644
+ function updateContextSelector < T , S > (
645
+ context : ReactContext < T > ,
646
+ selector : T => S ,
647
+ ) : S {
648
+ const hook = updateWorkInProgressHook ( ) ;
649
+ let [ previousContext , previousSelector , previousSelect ] = hook . memoizedState ;
650
+
651
+ if ( context !== previousContext || selector !== previousSelector ) {
652
+ // context and or selector have changed. we need to discard memoizedState
653
+ // and recreate our select function
654
+ let select = makeSelect ( context , selector ) ;
655
+ let [ selection ] = selectFromContext ( context , select ) ;
656
+ hook . memoizedState = [ context , selector , select ] ;
657
+ return selection ;
658
+ } else {
659
+ return selectFromContext ( context , previousSelect ) [ 0 ] ;
660
+ }
661
+ }
662
+
605
663
function createFunctionComponentUpdateQueue ( ) : FunctionComponentUpdateQueue {
606
664
return {
607
665
lastEffect : null ,
@@ -1223,6 +1281,7 @@ export const ContextOnlyDispatcher: Dispatcher = {
1223
1281
1224
1282
useCallback : throwInvalidHookError ,
1225
1283
useContext : throwInvalidHookError ,
1284
+ useContextSelector : throwInvalidHookError ,
1226
1285
useEffect : throwInvalidHookError ,
1227
1286
useImperativeHandle : throwInvalidHookError ,
1228
1287
useLayoutEffect : throwInvalidHookError ,
@@ -1238,6 +1297,7 @@ const HooksDispatcherOnMount: Dispatcher = {
1238
1297
1239
1298
useCallback : mountCallback ,
1240
1299
useContext : readContext ,
1300
+ useContextSelector : mountContextSelector ,
1241
1301
useEffect : mountEffect ,
1242
1302
useImperativeHandle : mountImperativeHandle ,
1243
1303
useLayoutEffect : mountLayoutEffect ,
@@ -1253,6 +1313,7 @@ const HooksDispatcherOnUpdate: Dispatcher = {
1253
1313
1254
1314
useCallback : updateCallback ,
1255
1315
useContext : readContext ,
1316
+ useContextSelector : updateContextSelector ,
1256
1317
useEffect : updateEffect ,
1257
1318
useImperativeHandle : updateImperativeHandle ,
1258
1319
useLayoutEffect : updateLayoutEffect ,
@@ -1312,6 +1373,11 @@ if (__DEV__) {
1312
1373
mountHookTypesDev ( ) ;
1313
1374
return readContext ( context , observedBits ) ;
1314
1375
} ,
1376
+ useContextSelector< T , S > (context: ReactContext< T > , selector: T => S ) : S {
1377
+ currentHookNameInDev = 'useContextSelector' ;
1378
+ mountHookTypesDev ( ) ;
1379
+ return mountContextSelector ( context , selector ) ;
1380
+ } ,
1315
1381
useEffect(
1316
1382
create: () => ( ( ) => void ) | void ,
1317
1383
deps : Array < mixed > | void | null,
@@ -1413,6 +1479,11 @@ if (__DEV__) {
1413
1479
updateHookTypesDev ( ) ;
1414
1480
return readContext ( context , observedBits ) ;
1415
1481
} ,
1482
+ useContextSelector< T , S > (context: ReactContext< T > , selector: T => S ) : S {
1483
+ currentHookNameInDev = 'useContextSelector' ;
1484
+ updateHookTypesDev ( ) ;
1485
+ return mountContextSelector ( context , selector ) ;
1486
+ } ,
1416
1487
useEffect(
1417
1488
create: () => ( ( ) => void ) | void ,
1418
1489
deps : Array < mixed > | void | null,
@@ -1510,6 +1581,11 @@ if (__DEV__) {
1510
1581
updateHookTypesDev ( ) ;
1511
1582
return readContext ( context , observedBits ) ;
1512
1583
} ,
1584
+ useContextSelector< T , S > (context: ReactContext< T > , selector: T => S ) : S {
1585
+ currentHookNameInDev = 'useContextSelector' ;
1586
+ updateHookTypesDev ( ) ;
1587
+ return updateContextSelector ( context , selector ) ;
1588
+ } ,
1513
1589
useEffect(
1514
1590
create: () => ( ( ) => void ) | void ,
1515
1591
deps : Array < mixed > | void | null,
@@ -1610,6 +1686,12 @@ if (__DEV__) {
1610
1686
mountHookTypesDev ( ) ;
1611
1687
return readContext ( context , observedBits ) ;
1612
1688
} ,
1689
+ useContextSelector< T , S > (context: ReactContext< T > , selector: T => S ) : S {
1690
+ currentHookNameInDev = 'useContextSelector' ;
1691
+ warnInvalidHookAccess ( ) ;
1692
+ mountHookTypesDev ( ) ;
1693
+ return mountContextSelector ( context , selector ) ;
1694
+ } ,
1613
1695
useEffect(
1614
1696
create: () => ( ( ) => void ) | void ,
1615
1697
deps : Array < mixed > | void | null,
@@ -1718,6 +1800,12 @@ if (__DEV__) {
1718
1800
updateHookTypesDev ( ) ;
1719
1801
return readContext ( context , observedBits ) ;
1720
1802
} ,
1803
+ useContextSelector< T , S > (context: ReactContext< T > , selector: T => S ) : S {
1804
+ currentHookNameInDev = 'useContextSelector' ;
1805
+ warnInvalidHookAccess ( ) ;
1806
+ updateHookTypesDev ( ) ;
1807
+ return updateContextSelector ( context , selector ) ;
1808
+ } ,
1721
1809
useEffect(
1722
1810
create: () => ( ( ) => void ) | void ,
1723
1811
deps : Array < mixed > | void | null,
0 commit comments