Skip to content

Commit

Permalink
add no-op check
Browse files Browse the repository at this point in the history
  • Loading branch information
EskiMojo14 authored and markerikson committed Jun 13, 2023
1 parent 1812a78 commit a5e9a43
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 35 deletions.
5 changes: 3 additions & 2 deletions src/components/Context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createContext } from 'react'
import type { Context } from 'react'
import type { Action, AnyAction, Store } from 'redux'
import type { Subscription } from '../utils/Subscription'
import { StabilityCheck } from '../hooks/useSelector'
import { CheckFrequency } from '../hooks/useSelector'

export interface ReactReduxContextValue<
SS = any,
Expand All @@ -11,7 +11,8 @@ export interface ReactReduxContextValue<
store: Store<SS, A>
subscription: Subscription
getServerState?: () => SS
stabilityCheck: StabilityCheck
stabilityCheck: CheckFrequency
noopCheck: CheckFrequency
}

let realContext: Context<ReactReduxContextValue> | null = null
Expand Down
11 changes: 8 additions & 3 deletions src/components/Provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ReactReduxContext, ReactReduxContextValue } from './Context'
import { createSubscription } from '../utils/Subscription'
import { useIsomorphicLayoutEffect } from '../utils/useIsomorphicLayoutEffect'
import { Action, AnyAction, Store } from 'redux'
import { StabilityCheck } from '../hooks/useSelector'
import { CheckFrequency } from '../hooks/useSelector'

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

/** Global configuration for the `useSelector` stability check */
stabilityCheck?: StabilityCheck
stabilityCheck?: CheckFrequency

/** Global configuration for the `useSelector` no-op check */
noopCheck?: CheckFrequency

children: ReactNode
}
Expand All @@ -35,6 +38,7 @@ function Provider<A extends Action = AnyAction, S = unknown>({
children,
serverState,
stabilityCheck = 'once',
noopCheck = 'once',
}: ProviderProps<A, S>) {
const contextValue = useMemo(() => {
const subscription = createSubscription(store)
Expand All @@ -43,8 +47,9 @@ function Provider<A extends Action = AnyAction, S = unknown>({
subscription,
getServerState: serverState ? () => serverState : undefined,
stabilityCheck,
noopCheck,
}
}, [store, serverState, stabilityCheck])
}, [store, serverState, stabilityCheck, noopCheck])

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

Expand Down
83 changes: 53 additions & 30 deletions src/hooks/useSelector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ import type { EqualityFn, NoInfer } from '../types'
import type { uSESWS } from '../utils/useSyncExternalStore'
import { notInitialized } from '../utils/useSyncExternalStore'

export type StabilityCheck = 'never' | 'once' | 'always'
export type CheckFrequency = 'never' | 'once' | 'always'

export interface UseSelectorOptions<Selected = unknown> {
equalityFn?: EqualityFn<Selected>
stabilityCheck?: StabilityCheck
stabilityCheck?: CheckFrequency
noopCheck?: CheckFrequency
}

interface UseSelector {
Expand Down Expand Up @@ -52,10 +53,13 @@ export function createSelectorHook(context = ReactReduxContext): UseSelector {
| EqualityFn<NoInfer<Selected>>
| UseSelectorOptions<NoInfer<Selected>> = {}
): Selected {
const { equalityFn = refEquality, stabilityCheck = undefined } =
typeof equalityFnOrOptions === 'function'
? { equalityFn: equalityFnOrOptions }
: equalityFnOrOptions
const {
equalityFn = refEquality,
stabilityCheck = undefined,
noopCheck = undefined,
} = typeof equalityFnOrOptions === 'function'
? { equalityFn: equalityFnOrOptions }
: equalityFnOrOptions
if (process.env.NODE_ENV !== 'production') {
if (!selector) {
throw new Error(`You must pass a selector to useSelector`)
Expand All @@ -75,6 +79,7 @@ export function createSelectorHook(context = ReactReduxContext): UseSelector {
subscription,
getServerState,
stabilityCheck: globalStabilityCheck,
noopCheck: globalNoopCheck,
} = useReduxContext()!

const firstRun = useRef(true)
Expand All @@ -83,31 +88,49 @@ export function createSelectorHook(context = ReactReduxContext): UseSelector {
{
[selector.name](state: TState) {
const selected = selector(state)
const finalStabilityCheck =
// are we safe to use ?? here?
typeof stabilityCheck === 'undefined'
? globalStabilityCheck
: stabilityCheck
if (
process.env.NODE_ENV !== 'production' &&
(finalStabilityCheck === 'always' ||
(finalStabilityCheck === 'once' && firstRun.current))
) {
const toCompare = selector(state)
if (!equalityFn(selected, toCompare)) {
console.warn(
'Selector ' +
(selector.name || 'unknown') +
' returned a different result when called with the same parameters. This can lead to unnecessary rerenders.' +
'\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',
{
state,
selected,
selected2: toCompare,
}
)
if (process.env.NODE_ENV !== 'production') {
const finalStabilityCheck =
// are we safe to use ?? here?
typeof stabilityCheck === 'undefined'
? globalStabilityCheck
: stabilityCheck
if (
finalStabilityCheck === 'always' ||
(finalStabilityCheck === 'once' && firstRun.current)
) {
const toCompare = selector(state)
if (!equalityFn(selected, toCompare)) {
console.warn(
'Selector ' +
(selector.name || 'unknown') +
' returned a different result when called with the same parameters. This can lead to unnecessary rerenders.' +
'\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',
{
state,
selected,
selected2: toCompare,
}
)
}
}
const finalNoopCheck =
// are we safe to use ?? here?
typeof noopCheck === 'undefined' ? globalNoopCheck : noopCheck
if (
finalNoopCheck === 'always' ||
(finalNoopCheck === 'once' && firstRun.current)
) {
// @ts-ignore
if (selected === state) {
console.warn(
'Selector ' +
(selector.name || 'unknown') +
' returned the root state when called. This can lead to unnecessary rerenders.' +
'\nSelectors that return the entire state are almost certainly a mistake, as they will cause a rerender whenever *anything* in state changes.'
)
}
}
firstRun.current = false
if (firstRun.current) firstRun.current = false
}
return selected
},
Expand Down

0 comments on commit a5e9a43

Please sign in to comment.