Skip to content

Commit 45dfd45

Browse files
authored
Assorted docs cleanup (#1688)
1 parent a923553 commit 45dfd45

File tree

9 files changed

+5736
-5689
lines changed

9 files changed

+5736
-5689
lines changed

docs/api/hooks.md

Lines changed: 93 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@ React's new ["hooks" APIs](https://reactjs.org/docs/hooks-intro.html) give funct
1111

1212
React Redux now offers a set of hook APIs as an alternative to the existing `connect()` Higher Order Component. These APIs allow you to subscribe to the Redux store and dispatch actions, without having to wrap your components in `connect()`.
1313

14+
:::tip
15+
16+
**We recommend using the React-Redux hooks API as the default approach in your React components.**
17+
18+
The existing `connect` API still works and will continue to be supported, but the hooks API is simpler and works better with TypeScript.
19+
20+
:::
21+
1422
These hooks were first added in v7.1.0.
1523

1624
## Using Hooks in a React Redux App
@@ -38,7 +46,11 @@ const result: any = useSelector(selector: Function, equalityFn?: Function)
3846

3947
Allows you to extract data from the Redux store state, using a selector function.
4048

41-
> **Note**: The selector function should be [pure](https://en.wikipedia.org/wiki/Pure_function) since it is potentially executed multiple times and at arbitrary points in time.
49+
:::info
50+
51+
The selector function should be [pure](https://en.wikipedia.org/wiki/Pure_function) since it is potentially executed multiple times and at arbitrary points in time.
52+
53+
:::
4254

4355
The selector is approximately equivalent to the [`mapStateToProps` argument to `connect`](../using-react-redux/connect-mapstate) conceptually. The selector will be called with the entire Redux store state as its only argument. The selector will be run whenever the function component renders (unless its reference hasn't changed since a previous render of the component so that a cached result can be returned by the hook without re-running the selector). `useSelector()` will also subscribe to the Redux store, and run your selector whenever an action is dispatched.
4456

@@ -50,7 +62,11 @@ However, there are some differences between the selectors passed to `useSelector
5062
- Extra care must be taken when using memoizing selectors (see examples below for more details).
5163
- `useSelector()` uses strict `===` reference equality checks by default, not shallow equality (see the following section for more details).
5264

53-
> **Note**: There are potential edge cases with using props in selectors that may cause errors. See the [Usage Warnings](#usage-warnings) section of this page for further details.
65+
:::info
66+
67+
There are potential edge cases with using props in selectors that may cause issues. See the [Usage Warnings](#usage-warnings) section of this page for further details.
68+
69+
:::
5470

5571
You may call `useSelector()` multiple times within a single function component. Each call to `useSelector()` creates an individual subscription to the Redux store. Because of the React update batching behavior used in React Redux v7, a dispatched action that causes multiple `useSelector()`s in the same component to return new values _should_ only result in a single re-render.
5672

@@ -91,7 +107,7 @@ import React from 'react'
91107
import { useSelector } from 'react-redux'
92108

93109
export const CounterComponent = () => {
94-
const counter = useSelector((state) => state.counter)
110+
const counter = useSelector(state => state.counter)
95111
return <div>{counter}</div>
96112
}
97113
```
@@ -102,8 +118,8 @@ Using props via closure to determine what to extract:
102118
import React from 'react'
103119
import { useSelector } from 'react-redux'
104120

105-
export const TodoListItem = (props) => {
106-
const todo = useSelector((state) => state.todos[props.id])
121+
export const TodoListItem = props => {
122+
const todo = useSelector(state => state.todos[props.id])
107123
return <div>{todo.text}</div>
108124
}
109125
```
@@ -119,21 +135,21 @@ import React from 'react'
119135
import { useSelector } from 'react-redux'
120136
import { createSelector } from 'reselect'
121137

122-
const selectNumOfDoneTodos = createSelector(
123-
(state) => state.todos,
124-
(todos) => todos.filter((todo) => todo.isDone).length
138+
const selectNumCompletedTodos = createSelector(
139+
state => state.todos,
140+
todos => todos.filter(todo => todo.completed).length
125141
)
126142

127-
export const DoneTodosCounter = () => {
128-
const numOfDoneTodos = useSelector(selectNumOfDoneTodos)
129-
return <div>{numOfDoneTodos}</div>
143+
export const CompletedTodosCounter = () => {
144+
const numCompletedTodos = useSelector(selectNumCompletedTodos)
145+
return <div>{numCompletedTodos}</div>
130146
}
131147

132148
export const App = () => {
133149
return (
134150
<>
135-
<span>Number of done todos:</span>
136-
<DoneTodosCounter />
151+
<span>Number of completed todos:</span>
152+
<CompletedTodosCounter />
137153
</>
138154
)
139155
}
@@ -146,25 +162,26 @@ import React from 'react'
146162
import { useSelector } from 'react-redux'
147163
import { createSelector } from 'reselect'
148164

149-
const selectNumOfTodosWithIsDoneValue = createSelector(
150-
(state) => state.todos,
151-
(_, isDone) => isDone,
152-
(todos, isDone) => todos.filter((todo) => todo.isDone === isDone).length
165+
const selectCompletedTodosCount = createSelector(
166+
state => state.todos,
167+
(_, completed) => completed,
168+
(todos, completed) =>
169+
todos.filter(todo => todo.completed === completed).length
153170
)
154171

155-
export const TodoCounterForIsDoneValue = ({ isDone }) => {
156-
const NumOfTodosWithIsDoneValue = useSelector((state) =>
157-
selectNumOfTodosWithIsDoneValue(state, isDone)
172+
export const CompletedTodosCount = ({ completed }) => {
173+
const matchingCount = useSelector(state =>
174+
selectCompletedTodosCount(state, completed)
158175
)
159176

160-
return <div>{NumOfTodosWithIsDoneValue}</div>
177+
return <div>{matchingCount}</div>
161178
}
162179

163180
export const App = () => {
164181
return (
165182
<>
166183
<span>Number of done todos:</span>
167-
<TodoCounterForIsDoneValue isDone={true} />
184+
<CompletedTodosCount completed={true} />
168185
</>
169186
)
170187
}
@@ -177,40 +194,36 @@ import React, { useMemo } from 'react'
177194
import { useSelector } from 'react-redux'
178195
import { createSelector } from 'reselect'
179196

180-
const makeNumOfTodosWithIsDoneSelector = () =>
197+
const makeSelectCompletedTodosCount = () =>
181198
createSelector(
182-
(state) => state.todos,
183-
(_, isDone) => isDone,
184-
(todos, isDone) => todos.filter((todo) => todo.isDone === isDone).length
199+
state => state.todos,
200+
(_, completed) => completed,
201+
(todos, completed) =>
202+
todos.filter(todo => todo.completed === completed).length
185203
)
186204

187-
export const TodoCounterForIsDoneValue = ({ isDone }) => {
188-
const selectNumOfTodosWithIsDone = useMemo(
189-
makeNumOfTodosWithIsDoneSelector,
190-
[]
191-
)
205+
export const CompletedTodosCount = ({ completed }) => {
206+
const selectCompletedTodosCount = useMemo(makeSelectCompletedTodosCount, [])
192207

193-
const numOfTodosWithIsDoneValue = useSelector((state) =>
194-
selectNumOfTodosWithIsDone(state, isDone)
208+
const matchingCount = useSelector(state =>
209+
selectCompletedTodosCount(state, completed)
195210
)
196211

197-
return <div>{numOfTodosWithIsDoneValue}</div>
212+
return <div>{matchingCount}</div>
198213
}
199214

200215
export const App = () => {
201216
return (
202217
<>
203218
<span>Number of done todos:</span>
204-
<TodoCounterForIsDoneValue isDone={true} />
219+
<CompletedTodosCount completed={true} />
205220
<span>Number of unfinished todos:</span>
206-
<TodoCounterForIsDoneValue isDone={false} />
221+
<CompletedTodosCount completed={false} />
207222
</>
208223
)
209224
}
210225
```
211226

212-
## Removed: `useActions()`
213-
214227
## `useDispatch()`
215228

216229
```js
@@ -219,8 +232,6 @@ const dispatch = useDispatch()
219232

220233
This hook returns a reference to the `dispatch` function from the Redux store. You may use it to dispatch actions as needed.
221234

222-
_Note: like in [React's `useReducer`](https://reactjs.org/docs/hooks-reference.html#usereducer), the returned `dispatch` function identity is stable and won't change on re-renders (unless you change the `store` being passed to the `<Provider>`, which would be extremely unusual)._
223-
224235
#### Examples
225236

226237
```jsx
@@ -241,7 +252,7 @@ export const CounterComponent = ({ value }) => {
241252
}
242253
```
243254

244-
Reminder: when passing a callback using `dispatch` to a child component, you should memoize it with [`useCallback`](https://reactjs.org/docs/hooks-reference.html#usecallback), just like you should memoize any passed callback. This avoids unnecessary rendering of child components due to the changed callback reference. You can safely pass `[dispatch]` in the dependency array for the `useCallback` call - since `dispatch` won't change, the callback will be reused properly (as it should). For example:
255+
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.
245256

246257
```jsx
247258
import React, { useCallback } from 'react'
@@ -267,6 +278,28 @@ export const MyIncrementButton = React.memo(({ onIncrement }) => (
267278
))
268279
```
269280

281+
:::info
282+
283+
The `dispatch` function reference will be stable as long as the same store instance is being passed to the `<Provider>`.
284+
Normally, that store instance never changes in an application.
285+
286+
However, the React hooks lint rules do not know that `dispatch` should be stable, and will warn that the `dispatch` variable
287+
should be added to dependency arrays for `useEffect` and `useCallback`. The simplest solution is to do just that:
288+
289+
````js
290+
export const Todos() = () => {
291+
const dispatch = useDispatch();
292+
293+
useEffect(() => {
294+
dispatch(fetchTodos())
295+
// highlight-start
296+
// Safe to add dispatch to the dependencies array
297+
}, [dispatch])
298+
// highlight-end
299+
}
300+
301+
:::
302+
270303
## `useStore()`
271304

272305
```js
@@ -304,7 +337,7 @@ import {
304337
Provider,
305338
createStoreHook,
306339
createDispatchHook,
307-
createSelectorHook,
340+
createSelectorHook
308341
} from 'react-redux'
309342
310343
const MyContext = React.createContext(null)
@@ -329,6 +362,12 @@ export function MyProvider({ children }) {
329362
330363
### Stale Props and "Zombie Children"
331364
365+
:::info
366+
367+
The React-Redux hooks API has been production-ready since we released it in v7.1.0, and **we recommend using the hooks API as the default approach in your components**. However, there are a couple of edge cases that can occur, and **we're documenting those so that you can be aware of them**.
368+
369+
:::
370+
332371
One of the most difficult aspects of React Redux's implementation is ensuring that if your `mapStateToProps` function is defined as `(state, ownProps)`, it will be called with the "latest" props every time. Up through version 4, there were recurring bugs reported involving edge case situations, such as errors thrown from a `mapState` function for a list item whose data had just been deleted.
333372
334373
Starting with version 5, React Redux has attempted to guarantee that consistency with `ownProps`. In version 7, that is implemented using a custom `Subscription` class internally in `connect()`, which forms a nested hierarchy. This ensures that connected components lower in the tree will only receive store update notifications once the nearest connected ancestor has been updated. However, this relies on each `connect()` instance overriding part of the internal React context, supplying its own unique `Subscription` instance to form that nesting, and rendering the `<ReactReduxContext.Provider>` with that new context value.
@@ -358,7 +397,15 @@ If you prefer to deal with this issue yourself, here are some possible options f
358397
- In cases where you do rely on props in your selector function _and_ those props may change over time, _or_ the data you're extracting may be based on items that can be deleted, try writing the selector functions defensively. Don't just reach straight into `state.todos[props.id].name` - read `state.todos[props.id]` first, and verify that it exists before trying to read `todo.name`.
359398
- Because `connect` adds the necessary `Subscription` to the context provider and delays evaluating child subscriptions until the connected component has re-rendered, putting a connected component in the component tree just above the component using `useSelector` will prevent these issues as long as the connected component gets re-rendered due to the same store update as the hooks component.
360399

361-
> **Note**: For a longer description of this issue, see ["Stale props and zombie children in Redux" by Kai Hao](https://kaihao.dev/posts/Stale-props-and-zombie-children-in-Redux), [this chat log that describes the problems in more detail](https://gist.github.com/markerikson/faac6ae4aca7b82a058e13216a7888ec), and [issue #1179](https://github.com/reduxjs/react-redux/issues/1179).
400+
:::info
401+
402+
For a longer description of these scenarios, see:
403+
404+
- ["Stale props and zombie children in Redux" by Kai Hao](https://kaihao.dev/posts/Stale-props-and-zombie-children-in-Redux)
405+
- [this chat log that describes the problems in more detail](https://gist.github.com/markerikson/faac6ae4aca7b82a058e13216a7888ec)
406+
- [issue #1179](https://github.com/reduxjs/react-redux/issues/1179)
407+
408+
:::
362409

363410
### Performance
364411

@@ -368,7 +415,7 @@ If further performance optimizations are necessary, you may consider wrapping yo
368415

369416
```jsx
370417
const CounterComponent = ({ name }) => {
371-
const counter = useSelector((state) => state.counter)
418+
const counter = useSelector(state => state.counter)
372419
return (
373420
<div>
374421
{name}: {counter}
@@ -409,7 +456,7 @@ export function useActions(actions, deps) {
409456
return useMemo(
410457
() => {
411458
if (Array.isArray(actions)) {
412-
return actions.map((a) => bindActionCreators(a, dispatch))
459+
return actions.map(a => bindActionCreators(a, dispatch))
413460
}
414461
return bindActionCreators(actions, dispatch)
415462
},
@@ -431,3 +478,4 @@ export function useShallowEqualSelector(selector) {
431478
### Additional considerations when using hooks
432479
433480
There are some architectural trade offs to take into consideration when deciding whether to use hooks or not. Mark Erikson summarizes these nicely in his two blog posts [Thoughts on React Hooks, Redux, and Separation of Concerns](https://blog.isquaredsoftware.com/2019/07/blogged-answers-thoughts-on-hooks/) and [Hooks, HOCs, and Tradeoffs](https://blog.isquaredsoftware.com/2019/09/presentation-hooks-hocs-tradeoffs/).
481+
````

website/.gitignore

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# dependencies
2+
/node_modules
3+
4+
# production
5+
/build
6+
7+
# generated files
8+
.docusaurus/
9+
website/.docusaurus/
10+
.cache-loader
11+
12+
# misc
13+
.DS_Store
14+
.env.local
15+
.env.development.local
16+
.env.test.local
17+
.env.production.local
18+
19+
npm-debug.log*
20+
yarn-debug.log*
21+
yarn-error.log*

0 commit comments

Comments
 (0)