1- import { useDebugValue } from 'react'
1+ import { useCallback , useDebugValue , useRef } from 'react'
22
33import {
44 createReduxContextHook ,
@@ -9,6 +9,24 @@ import type { EqualityFn, NoInfer } from '../types'
99import type { uSESWS } from '../utils/useSyncExternalStore'
1010import { notInitialized } from '../utils/useSyncExternalStore'
1111
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+
1230let useSyncExternalStoreWithSelector = notInitialized as uSESWS
1331export const initializeUseSelector = ( fn : uSESWS ) => {
1432 useSyncExternalStoreWithSelector = fn
@@ -22,21 +40,22 @@ const refEquality: EqualityFn<any> = (a, b) => a === b
2240 * @param {React.Context } [context=ReactReduxContext] Context passed to your `<Provider>`.
2341 * @returns {Function } A `useSelector` hook bound to the specified context.
2442 */
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 {
3144 const useReduxContext =
3245 context === ReactReduxContext
3346 ? useDefaultReduxContext
3447 : createReduxContextHook ( context )
3548
3649 return function useSelector < TState , Selected extends unknown > (
3750 selector : ( state : TState ) => Selected ,
38- equalityFn : EqualityFn < NoInfer < Selected > > = refEquality
51+ equalityFnOrOptions :
52+ | EqualityFn < NoInfer < Selected > >
53+ | UseSelectorOptions < NoInfer < Selected > > = { }
3954 ) : Selected {
55+ const { equalityFn = refEquality , stabilityCheck = undefined } =
56+ typeof equalityFnOrOptions === 'function'
57+ ? { equalityFn : equalityFnOrOptions }
58+ : equalityFnOrOptions
4059 if ( process . env . NODE_ENV !== 'production' ) {
4160 if ( ! selector ) {
4261 throw new Error ( `You must pass a selector to useSelector` )
@@ -51,13 +70,56 @@ export function createSelectorHook(
5170 }
5271 }
5372
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+ )
55117
56118 const selectedState = useSyncExternalStoreWithSelector (
57119 subscription . addNestedSub ,
58120 store . getState ,
59121 getServerState || store . getState ,
60- selector ,
122+ wrappedSelector ,
61123 equalityFn
62124 )
63125
0 commit comments