Skip to content

Commit f3b52f5

Browse files
authored
Merge pull request #2091 from aryaemami59/identity-function
2 parents b748c5c + 07f3060 commit f3b52f5

File tree

7 files changed

+160
-99
lines changed

7 files changed

+160
-99
lines changed

docs/api/hooks.md

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,14 @@ From there, you may import any of the listed React Redux hooks APIs and use them
4848
type RootState = ReturnType<typeof store.getState>
4949
type SelectorFn = <Selected>(state: RootState) => Selected
5050
type EqualityFn = (a: any, b: any) => boolean
51-
export type CheckFrequency = 'never' | 'once' | 'always'
51+
export type DevModeCheckFrequency = 'never' | 'once' | 'always'
5252

5353
interface UseSelectorOptions {
5454
equalityFn?: EqualityFn
55-
stabilityCheck?: CheckFrequency
56-
noopCheck?: CheckFrequency
55+
devModeChecks?: {
56+
stabilityCheck?: DevModeCheckFrequency
57+
identityFunctionCheck?: DevModeCheckFrequency
58+
}
5759
}
5860

5961
const result: Selected = useSelector(
@@ -296,14 +298,24 @@ By default, this will only happen when the selector is first called. You can con
296298

297299
```tsx title="Individual hook setting"
298300
function Component() {
299-
const count = useSelector(selectCount, { stabilityCheck: 'never' })
301+
const count = useSelector(selectCount, {
302+
devModeChecks: { stabilityCheck: 'never' },
303+
})
300304
// run once (default)
301-
const user = useSelector(selectUser, { stabilityCheck: 'once' })
305+
const user = useSelector(selectUser, {
306+
devModeChecks: { stabilityCheck: 'once' },
307+
})
302308
// ...
303309
}
304310
```
305311

306-
#### No-op selector check
312+
#### Identity Function (`state => state`) Check
313+
314+
:::danger Breaking Change!
315+
316+
This was previously referred to as `noopCheck`.
317+
318+
:::
307319

308320
In development, a check is conducted on the result returned by the selector. It warns in the console if the result is the same as the parameter passed in, i.e. the root state.
309321

@@ -321,16 +333,20 @@ const user = useSelector((state) => state.auth.currentUser)
321333
By default, this will only happen when the selector is first called. You can configure the check in the Provider or at each `useSelector` call.
322334

323335
```tsx title="Global setting via context"
324-
<Provider store={store} noopCheck="always">
336+
<Provider store={store} identityFunctionCheck="always">
325337
{children}
326338
</Provider>
327339
```
328340

329341
```tsx title="Individual hook setting"
330342
function Component() {
331-
const count = useSelector(selectCount, { noopCheck: 'never' })
343+
const count = useSelector(selectCount, {
344+
devModeChecks: { identityFunctionCheck: 'never' },
345+
})
332346
// run once (default)
333-
const user = useSelector(selectUser, { noopCheck: 'once' })
347+
const user = useSelector(selectUser, {
348+
devModeChecks: { identityFunctionCheck: 'once' },
349+
})
334350
// ...
335351
}
336352
```

docs/using-react-redux/connect-extracting-data-with-mapStateToProps.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ function mapStateToProps(state, ownProps) {
6969
}
7070

7171
// Later, in your application, a parent component renders:
72-
;<ConnectedTodo id={123} />
72+
<ConnectedTodo id={123} />
7373
// and your component receives props.id, props.todo, and props.visibilityFilter
7474
```
7575

src/components/Context.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
1-
import * as React from 'react'
21
import type { Context } from 'react'
2+
import * as React from 'react'
33
import type { Action, Store, UnknownAction } from 'redux'
44
import type { Subscription } from '../utils/Subscription'
5-
import type { CheckFrequency } from '../hooks/useSelector'
5+
import type { ProviderProps } from './Provider'
66

77
export interface ReactReduxContextValue<
88
SS = any,
99
A extends Action<string> = UnknownAction
10-
> {
10+
> extends Pick<ProviderProps, 'stabilityCheck' | 'identityFunctionCheck'> {
1111
store: Store<SS, A>
1212
subscription: Subscription
1313
getServerState?: () => SS
14-
stabilityCheck: CheckFrequency
15-
noopCheck: CheckFrequency
1614
}
1715

1816
const ContextKey = Symbol.for(`react-redux-context`)

src/components/Provider.tsx

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import type { Context, ReactNode } from 'react'
22
import * as React from 'react'
3-
import type { ReactReduxContextValue } from './Context'
4-
import { ReactReduxContext } from './Context'
3+
import type { Action, Store, UnknownAction } from 'redux'
4+
import type { DevModeCheckFrequency } from '../hooks/useSelector'
55
import { createSubscription } from '../utils/Subscription'
66
import { useIsomorphicLayoutEffect } from '../utils/useIsomorphicLayoutEffect'
7-
import type { Action, Store, UnknownAction } from 'redux'
8-
import type { CheckFrequency } from '../hooks/useSelector'
7+
import type { ReactReduxContextValue } from './Context'
8+
import { ReactReduxContext } from './Context'
99

1010
export interface ProviderProps<
1111
A extends Action<string> = UnknownAction,
@@ -29,11 +29,27 @@ export interface ProviderProps<
2929
*/
3030
context?: Context<ReactReduxContextValue<S, A> | null>
3131

32-
/** Global configuration for the `useSelector` stability check */
33-
stabilityCheck?: CheckFrequency
32+
/**
33+
* Determines the frequency of stability checks for all selectors.
34+
* This setting overrides the global configuration for
35+
* the `useSelector` stability check, allowing you to specify how often
36+
* these checks should occur in development mode.
37+
*
38+
* @since 8.1.0
39+
*/
40+
stabilityCheck?: DevModeCheckFrequency
3441

35-
/** Global configuration for the `useSelector` no-op check */
36-
noopCheck?: CheckFrequency
42+
/**
43+
* Determines the frequency of identity function checks for all selectors.
44+
* This setting overrides the global configuration for
45+
* the `useSelector` identity function check, allowing you to specify how often
46+
* these checks should occur in development mode.
47+
*
48+
* **Note**: Previously referred to as `noopCheck`.
49+
*
50+
* @since 9.0.0
51+
*/
52+
identityFunctionCheck?: DevModeCheckFrequency
3753

3854
children: ReactNode
3955
}
@@ -44,7 +60,7 @@ function Provider<A extends Action<string> = UnknownAction, S = unknown>({
4460
children,
4561
serverState,
4662
stabilityCheck = 'once',
47-
noopCheck = 'once',
63+
identityFunctionCheck = 'once',
4864
}: ProviderProps<A, S>) {
4965
const contextValue = React.useMemo(() => {
5066
const subscription = createSubscription(store)
@@ -53,9 +69,9 @@ function Provider<A extends Action<string> = UnknownAction, S = unknown>({
5369
subscription,
5470
getServerState: serverState ? () => serverState : undefined,
5571
stabilityCheck,
56-
noopCheck,
72+
identityFunctionCheck,
5773
}
58-
}, [store, serverState, stabilityCheck, noopCheck])
74+
}, [store, serverState, stabilityCheck, identityFunctionCheck])
5975

6076
const previousState = React.useMemo(() => store.getState(), [store])
6177

src/hooks/useSelector.ts

Lines changed: 71 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,68 @@
11
import * as React from 'react'
22

3-
import {
4-
createReduxContextHook,
5-
useReduxContext as useDefaultReduxContext,
6-
} from './useReduxContext'
73
import type { ReactReduxContextValue } from '../components/Context'
84
import { ReactReduxContext } from '../components/Context'
95
import type { EqualityFn, NoInfer } from '../types'
106
import type { uSESWS } from '../utils/useSyncExternalStore'
117
import { notInitialized } from '../utils/useSyncExternalStore'
8+
import {
9+
createReduxContextHook,
10+
useReduxContext as useDefaultReduxContext,
11+
} from './useReduxContext'
12+
13+
/**
14+
* The frequency of development mode checks.
15+
*
16+
* @since 8.1.0
17+
* @internal
18+
*/
19+
export type DevModeCheckFrequency = 'never' | 'once' | 'always'
20+
21+
/**
22+
* Represents the configuration for development mode checks.
23+
*
24+
* @since 9.0.0
25+
* @internal
26+
*/
27+
export interface DevModeChecks {
28+
/**
29+
* Overrides the global stability check for the selector.
30+
* - `once` - Run only the first time the selector is called.
31+
* - `always` - Run every time the selector is called.
32+
* - `never` - Never run the stability check.
33+
*
34+
* @default 'once'
35+
*
36+
* @since 8.1.0
37+
*/
38+
stabilityCheck: DevModeCheckFrequency
1239

13-
export type CheckFrequency = 'never' | 'once' | 'always'
40+
/**
41+
* Overrides the global identity function check for the selector.
42+
* - `once` - Run only the first time the selector is called.
43+
* - `always` - Run every time the selector is called.
44+
* - `never` - Never run the identity function check.
45+
*
46+
* **Note**: Previously referred to as `noopCheck`.
47+
*
48+
* @default 'once'
49+
*
50+
* @since 9.0.0
51+
*/
52+
identityFunctionCheck: DevModeCheckFrequency
53+
}
1454

1555
export interface UseSelectorOptions<Selected = unknown> {
1656
equalityFn?: EqualityFn<Selected>
17-
stabilityCheck?: CheckFrequency
18-
noopCheck?: CheckFrequency
57+
58+
/**
59+
* `useSelector` performs additional checks in development mode to help
60+
* identify and warn about potential issues in selector behavior. This
61+
* option allows you to customize the behavior of these checks per selector.
62+
*
63+
* @since 9.0.0
64+
*/
65+
devModeChecks?: Partial<DevModeChecks>
1966
}
2067

2168
export interface UseSelector {
@@ -59,13 +106,10 @@ export function createSelectorHook(
59106
| EqualityFn<NoInfer<Selected>>
60107
| UseSelectorOptions<NoInfer<Selected>> = {}
61108
): Selected {
62-
const {
63-
equalityFn = refEquality,
64-
stabilityCheck = undefined,
65-
noopCheck = undefined,
66-
} = typeof equalityFnOrOptions === 'function'
67-
? { equalityFn: equalityFnOrOptions }
68-
: equalityFnOrOptions
109+
const { equalityFn = refEquality, devModeChecks = {} } =
110+
typeof equalityFnOrOptions === 'function'
111+
? { equalityFn: equalityFnOrOptions }
112+
: equalityFnOrOptions
69113
if (process.env.NODE_ENV !== 'production') {
70114
if (!selector) {
71115
throw new Error(`You must pass a selector to useSelector`)
@@ -84,8 +128,8 @@ export function createSelectorHook(
84128
store,
85129
subscription,
86130
getServerState,
87-
stabilityCheck: globalStabilityCheck,
88-
noopCheck: globalNoopCheck,
131+
stabilityCheck,
132+
identityFunctionCheck,
89133
} = useReduxContext()
90134

91135
const firstRun = React.useRef(true)
@@ -95,10 +139,14 @@ export function createSelectorHook(
95139
[selector.name](state: TState) {
96140
const selected = selector(state)
97141
if (process.env.NODE_ENV !== 'production') {
98-
const finalStabilityCheck =
99-
typeof stabilityCheck === 'undefined'
100-
? globalStabilityCheck
101-
: stabilityCheck
142+
const {
143+
identityFunctionCheck: finalIdentityFunctionCheck,
144+
stabilityCheck: finalStabilityCheck,
145+
} = {
146+
stabilityCheck,
147+
identityFunctionCheck,
148+
...devModeChecks,
149+
}
102150
if (
103151
finalStabilityCheck === 'always' ||
104152
(finalStabilityCheck === 'once' && firstRun.current)
@@ -125,11 +173,9 @@ export function createSelectorHook(
125173
)
126174
}
127175
}
128-
const finalNoopCheck =
129-
typeof noopCheck === 'undefined' ? globalNoopCheck : noopCheck
130176
if (
131-
finalNoopCheck === 'always' ||
132-
(finalNoopCheck === 'once' && firstRun.current)
177+
finalIdentityFunctionCheck === 'always' ||
178+
(finalIdentityFunctionCheck === 'once' && firstRun.current)
133179
) {
134180
// @ts-ignore
135181
if (selected === state) {
@@ -153,7 +199,7 @@ export function createSelectorHook(
153199
return selected
154200
},
155201
}[selector.name],
156-
[selector, globalStabilityCheck, stabilityCheck]
202+
[selector, stabilityCheck, devModeChecks.stabilityCheck]
157203
)
158204

159205
const selectedState = useSyncExternalStoreWithSelector(

0 commit comments

Comments
 (0)