Skip to content

Commit a5e9a43

Browse files
EskiMojo14markerikson
authored andcommitted
add no-op check
1 parent 1812a78 commit a5e9a43

File tree

3 files changed

+64
-35
lines changed

3 files changed

+64
-35
lines changed

src/components/Context.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { createContext } from 'react'
22
import type { Context } from 'react'
33
import type { Action, AnyAction, Store } from 'redux'
44
import type { Subscription } from '../utils/Subscription'
5-
import { StabilityCheck } from '../hooks/useSelector'
5+
import { CheckFrequency } from '../hooks/useSelector'
66

77
export interface ReactReduxContextValue<
88
SS = any,
@@ -11,7 +11,8 @@ export interface ReactReduxContextValue<
1111
store: Store<SS, A>
1212
subscription: Subscription
1313
getServerState?: () => SS
14-
stabilityCheck: StabilityCheck
14+
stabilityCheck: CheckFrequency
15+
noopCheck: CheckFrequency
1516
}
1617

1718
let realContext: Context<ReactReduxContextValue> | null = null

src/components/Provider.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { ReactReduxContext, ReactReduxContextValue } from './Context'
33
import { createSubscription } from '../utils/Subscription'
44
import { useIsomorphicLayoutEffect } from '../utils/useIsomorphicLayoutEffect'
55
import { Action, AnyAction, Store } from 'redux'
6-
import { StabilityCheck } from '../hooks/useSelector'
6+
import { CheckFrequency } from '../hooks/useSelector'
77

88
export interface ProviderProps<A extends Action = AnyAction, S = unknown> {
99
/**
@@ -24,7 +24,10 @@ export interface ProviderProps<A extends Action = AnyAction, S = unknown> {
2424
context?: Context<ReactReduxContextValue<S, A>>
2525

2626
/** Global configuration for the `useSelector` stability check */
27-
stabilityCheck?: StabilityCheck
27+
stabilityCheck?: CheckFrequency
28+
29+
/** Global configuration for the `useSelector` no-op check */
30+
noopCheck?: CheckFrequency
2831

2932
children: ReactNode
3033
}
@@ -35,6 +38,7 @@ function Provider<A extends Action = AnyAction, S = unknown>({
3538
children,
3639
serverState,
3740
stabilityCheck = 'once',
41+
noopCheck = 'once',
3842
}: ProviderProps<A, S>) {
3943
const contextValue = useMemo(() => {
4044
const subscription = createSubscription(store)
@@ -43,8 +47,9 @@ function Provider<A extends Action = AnyAction, S = unknown>({
4347
subscription,
4448
getServerState: serverState ? () => serverState : undefined,
4549
stabilityCheck,
50+
noopCheck,
4651
}
47-
}, [store, serverState, stabilityCheck])
52+
}, [store, serverState, stabilityCheck, noopCheck])
4853

4954
const previousState = useMemo(() => store.getState(), [store])
5055

src/hooks/useSelector.ts

Lines changed: 53 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@ import type { EqualityFn, NoInfer } from '../types'
99
import type { uSESWS } from '../utils/useSyncExternalStore'
1010
import { notInitialized } from '../utils/useSyncExternalStore'
1111

12-
export type StabilityCheck = 'never' | 'once' | 'always'
12+
export type CheckFrequency = 'never' | 'once' | 'always'
1313

1414
export interface UseSelectorOptions<Selected = unknown> {
1515
equalityFn?: EqualityFn<Selected>
16-
stabilityCheck?: StabilityCheck
16+
stabilityCheck?: CheckFrequency
17+
noopCheck?: CheckFrequency
1718
}
1819

1920
interface UseSelector {
@@ -52,10 +53,13 @@ export function createSelectorHook(context = ReactReduxContext): UseSelector {
5253
| EqualityFn<NoInfer<Selected>>
5354
| UseSelectorOptions<NoInfer<Selected>> = {}
5455
): Selected {
55-
const { equalityFn = refEquality, stabilityCheck = undefined } =
56-
typeof equalityFnOrOptions === 'function'
57-
? { equalityFn: equalityFnOrOptions }
58-
: equalityFnOrOptions
56+
const {
57+
equalityFn = refEquality,
58+
stabilityCheck = undefined,
59+
noopCheck = undefined,
60+
} = typeof equalityFnOrOptions === 'function'
61+
? { equalityFn: equalityFnOrOptions }
62+
: equalityFnOrOptions
5963
if (process.env.NODE_ENV !== 'production') {
6064
if (!selector) {
6165
throw new Error(`You must pass a selector to useSelector`)
@@ -75,6 +79,7 @@ export function createSelectorHook(context = ReactReduxContext): UseSelector {
7579
subscription,
7680
getServerState,
7781
stabilityCheck: globalStabilityCheck,
82+
noopCheck: globalNoopCheck,
7883
} = useReduxContext()!
7984

8085
const firstRun = useRef(true)
@@ -83,31 +88,49 @@ export function createSelectorHook(context = ReactReduxContext): UseSelector {
8388
{
8489
[selector.name](state: TState) {
8590
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-
'\nSelectors 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-
)
91+
if (process.env.NODE_ENV !== 'production') {
92+
const finalStabilityCheck =
93+
// are we safe to use ?? here?
94+
typeof stabilityCheck === 'undefined'
95+
? globalStabilityCheck
96+
: stabilityCheck
97+
if (
98+
finalStabilityCheck === 'always' ||
99+
(finalStabilityCheck === 'once' && firstRun.current)
100+
) {
101+
const toCompare = selector(state)
102+
if (!equalityFn(selected, toCompare)) {
103+
console.warn(
104+
'Selector ' +
105+
(selector.name || 'unknown') +
106+
' returned a different result when called with the same parameters. This can lead to unnecessary rerenders.' +
107+
'\nSelectors 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',
108+
{
109+
state,
110+
selected,
111+
selected2: toCompare,
112+
}
113+
)
114+
}
115+
}
116+
const finalNoopCheck =
117+
// are we safe to use ?? here?
118+
typeof noopCheck === 'undefined' ? globalNoopCheck : noopCheck
119+
if (
120+
finalNoopCheck === 'always' ||
121+
(finalNoopCheck === 'once' && firstRun.current)
122+
) {
123+
// @ts-ignore
124+
if (selected === state) {
125+
console.warn(
126+
'Selector ' +
127+
(selector.name || 'unknown') +
128+
' returned the root state when called. This can lead to unnecessary rerenders.' +
129+
'\nSelectors that return the entire state are almost certainly a mistake, as they will cause a rerender whenever *anything* in state changes.'
130+
)
131+
}
109132
}
110-
firstRun.current = false
133+
if (firstRun.current) firstRun.current = false
111134
}
112135
return selected
113136
},

0 commit comments

Comments
 (0)