1
- import { useDebugValue } from 'react'
1
+ import { useCallback , useDebugValue , useRef } from 'react'
2
2
3
3
import {
4
4
createReduxContextHook ,
@@ -9,6 +9,24 @@ import type { EqualityFn, NoInfer } from '../types'
9
9
import type { uSESWS } from '../utils/useSyncExternalStore'
10
10
import { notInitialized } from '../utils/useSyncExternalStore'
11
11
12
+ export type StabilityCheck = 'never' | 'once' | 'always'
13
+
14
+ export interface UseSelectorOptions < Selected = unknown > {
15
+ equalityFn ?: EqualityFn < Selected >
16
+ stabilityCheck ?: StabilityCheck
17
+ }
18
+
19
+ interface UseSelector {
20
+ < TState = unknown , Selected = unknown > (
21
+ selector : ( state : TState ) => Selected ,
22
+ equalityFn ?: EqualityFn < Selected >
23
+ ) : Selected
24
+ < TState = unknown , Selected = unknown> (
25
+ selector : ( state : TState ) => Selected ,
26
+ options ?: UseSelectorOptions < Selected >
27
+ ) : Selected
28
+ }
29
+
12
30
let useSyncExternalStoreWithSelector = notInitialized as uSESWS
13
31
export const initializeUseSelector = ( fn : uSESWS ) => {
14
32
useSyncExternalStoreWithSelector = fn
@@ -22,21 +40,22 @@ const refEquality: EqualityFn<any> = (a, b) => a === b
22
40
* @param {React.Context } [context=ReactReduxContext] Context passed to your `<Provider>`.
23
41
* @returns {Function } A `useSelector` hook bound to the specified context.
24
42
*/
25
- export function createSelectorHook (
26
- context = ReactReduxContext
27
- ) : < TState = unknown , Selected = unknown > (
28
- selector : ( state : TState ) => Selected ,
29
- equalityFn ?: EqualityFn < Selected >
30
- ) => Selected {
43
+ export function createSelectorHook ( context = ReactReduxContext ) : UseSelector {
31
44
const useReduxContext =
32
45
context === ReactReduxContext
33
46
? useDefaultReduxContext
34
47
: createReduxContextHook ( context )
35
48
36
49
return function useSelector < TState , Selected extends unknown > (
37
50
selector : ( state : TState ) => Selected ,
38
- equalityFn : EqualityFn < NoInfer < Selected > > = refEquality
51
+ equalityFnOrOptions :
52
+ | EqualityFn < NoInfer < Selected > >
53
+ | UseSelectorOptions < NoInfer < Selected > > = { }
39
54
) : Selected {
55
+ const { equalityFn = refEquality , stabilityCheck = undefined } =
56
+ typeof equalityFnOrOptions === 'function'
57
+ ? { equalityFn : equalityFnOrOptions }
58
+ : equalityFnOrOptions
40
59
if ( process . env . NODE_ENV !== 'production' ) {
41
60
if ( ! selector ) {
42
61
throw new Error ( `You must pass a selector to useSelector` )
@@ -51,13 +70,56 @@ export function createSelectorHook(
51
70
}
52
71
}
53
72
54
- const { store, subscription, getServerState } = useReduxContext ( ) !
73
+ const {
74
+ store,
75
+ subscription,
76
+ getServerState,
77
+ stabilityCheck : globalStabilityCheck ,
78
+ } = useReduxContext ( ) !
79
+
80
+ const firstRun = useRef ( true )
81
+
82
+ const wrappedSelector = useCallback < typeof selector > (
83
+ {
84
+ [ selector . name ] ( state : TState ) {
85
+ const selected = selector ( state )
86
+ const finalStabilityCheck =
87
+ // are we safe to use ?? here?
88
+ typeof stabilityCheck === 'undefined'
89
+ ? globalStabilityCheck
90
+ : stabilityCheck
91
+ if (
92
+ process . env . NODE_ENV !== 'production' &&
93
+ ( finalStabilityCheck === 'always' ||
94
+ ( finalStabilityCheck === 'once' && firstRun . current ) )
95
+ ) {
96
+ const toCompare = selector ( state )
97
+ if ( ! equalityFn ( selected , toCompare ) ) {
98
+ console . warn (
99
+ 'Selector ' +
100
+ ( selector . name || 'unknown' ) +
101
+ ' returned a different result when called with the same parameters. This can lead to unnecessary rerenders.' +
102
+ '\n Selectors that return a new reference (such as an object or an array) should be memoized: https://redux.js.org/usage/deriving-data-selectors#optimizing-selectors-with-memoization' ,
103
+ {
104
+ state,
105
+ selected,
106
+ selected2 : toCompare ,
107
+ }
108
+ )
109
+ }
110
+ firstRun . current = false
111
+ }
112
+ return selected
113
+ } ,
114
+ } [ selector . name ] ,
115
+ [ selector , globalStabilityCheck , stabilityCheck ]
116
+ )
55
117
56
118
const selectedState = useSyncExternalStoreWithSelector (
57
119
subscription . addNestedSub ,
58
120
store . getState ,
59
121
getServerState || store . getState ,
60
- selector ,
122
+ wrappedSelector ,
61
123
equalityFn
62
124
)
63
125
0 commit comments