Skip to content

Commit bf08ea6

Browse files
authored
Merge pull request #2022 from reduxjs/no-op-check
2 parents 1812a78 + a18e8a9 commit bf08ea6

18 files changed

+235
-122
lines changed

.eslintrc

+26-6
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,16 @@
1111
"react": {
1212
"version": "detect"
1313
},
14-
"import/ignore": ["react-native"],
14+
"import/ignore": [
15+
"react-native"
16+
],
1517
"import/resolver": {
1618
"node": {
17-
"extensions": [".js", ".ts", ".tsx"]
19+
"extensions": [
20+
".js",
21+
".ts",
22+
".tsx"
23+
]
1824
}
1925
}
2026
},
@@ -38,12 +44,26 @@
3844
"react/jsx-wrap-multilines": 2,
3945
"react/no-string-refs": 0,
4046
"no-unused-vars": "off",
41-
"@typescript-eslint/no-unused-vars": ["error"],
47+
"@typescript-eslint/no-unused-vars": [
48+
"error"
49+
],
4250
"no-redeclare": "off",
43-
"@typescript-eslint/no-redeclare": ["error"]
51+
"@typescript-eslint/no-redeclare": [
52+
"error"
53+
],
54+
"@typescript-eslint/consistent-type-imports": [
55+
"error",
56+
{
57+
"prefer": "type-imports"
58+
}
59+
]
4460
},
45-
"plugins": ["@typescript-eslint", "import", "react"],
61+
"plugins": [
62+
"@typescript-eslint",
63+
"import",
64+
"react"
65+
],
4666
"globals": {
4767
"JSX": true
4868
}
49-
}
69+
}

docs/api/hooks.md

+38-5
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ description: 'API > Hooks: the `useSelector` and `useDispatch` hooks`'
1010

1111
# Hooks
1212

13-
React's new ["hooks" APIs](https://reactjs.org/docs/hooks-intro.html) give function components the ability to use local component state, execute side effects, and more. React also lets us write [custom hooks](https://reactjs.org/docs/hooks-custom.html), which let us extract reusable hooks to add our own behavior on top of React's built-in hooks.
13+
React's ["hooks" APIs](https://react.dev/reference/react#) give function components the ability to use local component state, execute side effects, and more. React also lets us write [custom hooks](https://react.dev/learn/reusing-logic-with-custom-hooks#extracting-your-own-custom-hook-from-a-component), which let us extract reusable hooks to add our own behavior on top of React's built-in hooks.
1414

1515
React Redux includes its own custom hook APIs, which allow your React components to subscribe to the Redux store and dispatch actions.
1616

@@ -48,11 +48,12 @@ 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 StabilityCheck = 'never' | 'once' | 'always'
51+
export type CheckFrequency = 'never' | 'once' | 'always'
5252

5353
interface UseSelectorOptions {
5454
equalityFn?: EqualityFn
55-
stabilityCheck?: StabilityCheck
55+
stabilityCheck?: CheckFrequency
56+
noopCheck?: CheckFrequency
5657
}
5758

5859
const result: Selected = useSelector(
@@ -272,7 +273,7 @@ These checks were first added in v8.1.0
272273

273274
In development, the provided selector function is run an extra time with the same parameter during the first call to `useSelector`, and warns in the console if the selector returns a different result (based on the `equalityFn` provided).
274275

275-
This is important, as a selector returning that returns a different result reference with the same parameter will cause unnecessary rerenders.
276+
This is important, as **a selector that returns a different result reference when called again with the same inputs will cause unnecessary rerenders**.
276277

277278
```ts
278279
// this selector will return a new object reference whenever called,
@@ -302,6 +303,38 @@ function Component() {
302303
}
303304
```
304305

306+
#### No-op selector check
307+
308+
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.
309+
310+
**A `useSelector` call returning the entire root state is almost always a mistake**, as it means the component will rerender whenever _anything_ in state changes. Selectors should be as granular as possible, like `state => state.some.nested.field`.
311+
312+
```ts no-transpile
313+
// BAD: this selector returns the entire state, meaning that the component will rerender unnecessarily
314+
const { count, user } = useSelector((state) => state)
315+
316+
// GOOD: instead, select only the state you need, calling useSelector as many times as needed
317+
const count = useSelector((state) => state.count.value)
318+
const user = useSelector((state) => state.auth.currentUser)
319+
```
320+
321+
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.
322+
323+
```tsx title="Global setting via context"
324+
<Provider store={store} noopCheck="always">
325+
{children}
326+
</Provider>
327+
```
328+
329+
```tsx title="Individual hook setting"
330+
function Component() {
331+
const count = useSelector(selectCount, { noopCheck: 'never' })
332+
// run once (default)
333+
const user = useSelector(selectUser, { noopCheck: 'once' })
334+
// ...
335+
}
336+
```
337+
305338
### Comparisons with `connect`
306339

307340
There are some differences between the selectors passed to `useSelector()` and a `mapState` function:
@@ -340,7 +373,7 @@ export const CounterComponent = ({ value }) => {
340373
}
341374
```
342375

343-
When passing a callback using `dispatch` to a child component, you may sometimes want to memoize it with [`useCallback`](https://reactjs.org/docs/hooks-reference.html#usecallback). _If_ the child component is trying to optimize render behavior using `React.memo()` or similar, this avoids unnecessary rendering of child components due to the changed callback reference.
376+
When passing a callback using `dispatch` to a child component, you may sometimes want to memoize it with [`useCallback`](https://react.dev/reference/react/useCallback). _If_ the child component is trying to optimize render behavior using `React.memo()` or similar, this avoids unnecessary rendering of child components due to the changed callback reference.
344377

345378
```jsx
346379
import React, { useCallback } from 'react'

docs/introduction/getting-started.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import 'react-lite-youtube-embed/dist/LiteYouTubeEmbed.css'
1313

1414
# Getting Started with React Redux
1515

16-
[React Redux](https://github.com/reduxjs/react-redux) is the official [React](https://reactjs.org/) UI bindings layer for [Redux](https://redux.js.org/). It lets your React components read data from a Redux store, and dispatch actions to the store to update state.
16+
[React Redux](https://github.com/reduxjs/react-redux) is the official [React](https://react.dev/) UI bindings layer for [Redux](https://redux.js.org/). It lets your React components read data from a Redux store, and dispatch actions to the store to update state.
1717

1818
## Installation
1919

docs/tutorials/quick-start.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ hide_title: true
1818
:::info Prerequisites
1919

2020
- Familiarity with [ES6 syntax and features](https://www.taniarascia.com/es6-syntax-and-feature-overview/)
21-
- Knowledge of React terminology: [JSX](https://reactjs.org/docs/introducing-jsx.html), [State](https://reactjs.org/docs/state-and-lifecycle.html), [Function Components, Props](https://reactjs.org/docs/components-and-props.html), and [Hooks](https://reactjs.org/docs/hooks-intro.html)
21+
- Knowledge of React terminology: [JSX](https://react.dev/learn/writing-markup-with-jsx), [State](https://react.dev/learn/state-a-components-memory), [Function Components, Props](https://react.dev/learn/passing-props-to-a-component), and [Hooks](https://react.dev/reference/react#)
2222
- Understanding of [Redux terms and concepts](https://redux.js.org/tutorials/fundamentals/part-2-concepts-data-flow)
2323

2424
:::

docs/tutorials/typescript.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ hide_title: true
1717

1818
:::info Prerequisites
1919

20-
- Knowledge of React [Hooks](https://reactjs.org/docs/hooks-intro.html)
20+
- Knowledge of React [Hooks](https://react.dev/reference/react#)
2121
- Understanding of [Redux terms and concepts](https://redux.js.org/tutorials/fundamentals/part-2-concepts-data-flow)
2222
- Understanding of TypeScript syntax and concepts
2323

docs/using-react-redux/accessing-store.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ connected components, or access the store directly. Here are some examples of ho
2222

2323
## Understanding Context Usage
2424

25-
Internally, React Redux uses [React's "context" feature](https://reactjs.org/docs/context.html) to make the
25+
Internally, React Redux uses [React's "context" feature](https://react.dev/learn/passing-data-deeply-with-context) to make the
2626
Redux store accessible to deeply nested connected components. As of React Redux version 6, this is normally handled
2727
by a single default context object instance generated by `React.createContext()`, called `ReactReduxContext`.
2828

src/components/Context.ts

+3-2
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 type { 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

+13-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
import React, { Context, ReactNode, useMemo } from 'react'
2-
import { ReactReduxContext, ReactReduxContextValue } from './Context'
1+
import type { Context, ReactNode } from 'react'
2+
import React, { useMemo } from 'react'
3+
import type { ReactReduxContextValue } from './Context'
4+
import { ReactReduxContext } from './Context'
35
import { createSubscription } from '../utils/Subscription'
46
import { useIsomorphicLayoutEffect } from '../utils/useIsomorphicLayoutEffect'
5-
import { Action, AnyAction, Store } from 'redux'
6-
import { StabilityCheck } from '../hooks/useSelector'
7+
import type { Action, AnyAction, Store } from 'redux'
8+
import type { CheckFrequency } from '../hooks/useSelector'
79

810
export interface ProviderProps<A extends Action = AnyAction, S = unknown> {
911
/**
@@ -24,7 +26,10 @@ export interface ProviderProps<A extends Action = AnyAction, S = unknown> {
2426
context?: Context<ReactReduxContextValue<S, A>>
2527

2628
/** Global configuration for the `useSelector` stability check */
27-
stabilityCheck?: StabilityCheck
29+
stabilityCheck?: CheckFrequency
30+
31+
/** Global configuration for the `useSelector` no-op check */
32+
noopCheck?: CheckFrequency
2833

2934
children: ReactNode
3035
}
@@ -35,6 +40,7 @@ function Provider<A extends Action = AnyAction, S = unknown>({
3540
children,
3641
serverState,
3742
stabilityCheck = 'once',
43+
noopCheck = 'once',
3844
}: ProviderProps<A, S>) {
3945
const contextValue = useMemo(() => {
4046
const subscription = createSubscription(store)
@@ -43,8 +49,9 @@ function Provider<A extends Action = AnyAction, S = unknown>({
4349
subscription,
4450
getServerState: serverState ? () => serverState : undefined,
4551
stabilityCheck,
52+
noopCheck,
4653
}
47-
}, [store, serverState, stabilityCheck])
54+
}, [store, serverState, stabilityCheck, noopCheck])
4855

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

src/components/connect.tsx

+8-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* eslint-disable valid-jsdoc, @typescript-eslint/no-unused-vars */
22
import hoistStatics from 'hoist-non-react-statics'
3-
import React, { ComponentType, useContext, useMemo, useRef } from 'react'
3+
import type { ComponentType } from 'react'
4+
import React, { useContext, useMemo, useRef } from 'react'
45
import { isValidElementType, isContextConsumer } from 'react-is'
56

67
import type { Store } from 'redux'
@@ -14,27 +15,29 @@ import type {
1415
ConnectPropsMaybeWithoutContext,
1516
} from '../types'
1617

17-
import defaultSelectorFactory, {
18+
import type {
1819
MapStateToPropsParam,
1920
MapDispatchToPropsParam,
2021
MergeProps,
2122
MapDispatchToPropsNonObject,
2223
SelectorFactoryOptions,
2324
} from '../connect/selectorFactory'
25+
import defaultSelectorFactory from '../connect/selectorFactory'
2426
import { mapDispatchToPropsFactory } from '../connect/mapDispatchToProps'
2527
import { mapStateToPropsFactory } from '../connect/mapStateToProps'
2628
import { mergePropsFactory } from '../connect/mergeProps'
2729

28-
import { createSubscription, Subscription } from '../utils/Subscription'
30+
import type { Subscription } from '../utils/Subscription'
31+
import { createSubscription } from '../utils/Subscription'
2932
import { useIsomorphicLayoutEffect } from '../utils/useIsomorphicLayoutEffect'
3033
import shallowEqual from '../utils/shallowEqual'
3134
import warning from '../utils/warning'
3235

33-
import {
34-
ReactReduxContext,
36+
import type {
3537
ReactReduxContextValue,
3638
ReactReduxContextInstance,
3739
} from './Context'
40+
import { ReactReduxContext } from './Context'
3841

3942
import type { uSES } from '../utils/useSyncExternalStore'
4043
import { notInitialized } from '../utils/useSyncExternalStore'

src/connect/wrapMapToProps.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { ActionCreatorsMapObject, Dispatch, ActionCreator } from 'redux'
1+
import type { ActionCreatorsMapObject, Dispatch, ActionCreator } from 'redux'
22

3-
import { FixTypeLater } from '../types'
3+
import type { FixTypeLater } from '../types'
44
import verifyPlainObject from '../utils/verifyPlainObject'
55

66
type AnyState = { [key: string]: any }

src/hooks/useDispatch.ts

+4-6
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
import { Action, AnyAction, Dispatch } from 'redux'
2-
import { Context } from 'react'
1+
import type { Action, AnyAction, Dispatch } from 'redux'
2+
import type { Context } from 'react'
33

4-
import {
5-
ReactReduxContext,
6-
ReactReduxContextValue,
7-
} from '../components/Context'
4+
import type { ReactReduxContextValue } from '../components/Context'
5+
import { ReactReduxContext } from '../components/Context'
86
import { useStore as useDefaultStore, createStoreHook } from './useStore'
97

108
/**

0 commit comments

Comments
 (0)