Skip to content

Commit edc1501

Browse files
authored
Hooks docs (#1249)
* Formatting * Add initial hooks API docs * Enable alpha docs versioning * Fix hooks page metadata
1 parent d4b54b5 commit edc1501

File tree

4 files changed

+452
-136
lines changed

4 files changed

+452
-136
lines changed

docs/api/hooks.md

Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
---
2+
id: hooks
3+
title: Hooks
4+
sidebar_label: Hooks
5+
hide_title: true
6+
---
7+
8+
# Hooks
9+
10+
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.
11+
12+
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()`.
13+
14+
> **Note**: The hook APIs listed in this page are **still experimental and in alpha!** We encourage you to try them out in your applications and give feedback, but be aware that they may be changed before a final release, including potential renaming or removal.
15+
16+
## Using Hooks in a React Redux App
17+
18+
As with `connect()`, you should start by wrapping your entire application in a `<Provider>` component to make the store available throughout the component tree:
19+
20+
```jsx
21+
const store = createStore(rootReducer)
22+
23+
ReactDOM.render(
24+
<Provider store={store}>
25+
<App />
26+
</Provider>,
27+
document.getElementById('root')
28+
)
29+
```
30+
31+
From there, you may import any of the listed React Redux hooks APIs and use them within your function components.
32+
33+
## `useSelector()`
34+
35+
```js
36+
const result : any = useSelector(selector : Function)
37+
```
38+
39+
Allows you to extract data from the Redux store state, using a selector function.
40+
41+
The selector is approximately equivalent to the [`mapStateToProps` argument to `connect`](../using-react-redux/connect-extracting-data-with-mapStateToProps.md) 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. `useSelector()` will also subscribe to the Redux store, and run your selector whenever an action is dispatched.
42+
43+
However, there are some differences between the selectors passed to `useSelector()` and a `mapState` function:
44+
45+
- The selector may return any value as a result, not just an object. The return value of the selector will be used as the return value of the `useSelector()` hook.
46+
- When an action is dispatched, `useSelector()` will do a shallow comparison of the previous selector result value and the current result value. If they are different, the component will be forced to re-render. If they are the same, they component will not re-render.
47+
- The selector function does _not_ receive an `ownProps` argument. If you wish to use props within the selector function to determine what values to extract, you should call the React [`useMemo()`]() or [`useCallback()`]() hooks yourself to create a version of the selector that will be re-created whenever the props it depends on change.
48+
49+
> **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.
50+
51+
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.
52+
53+
#### Examples
54+
55+
Basic usage:
56+
57+
```jsx
58+
import React from 'react'
59+
import { useSelector } from 'react-redux'
60+
61+
export const CounterComponent = () => {
62+
const counter = useSelector(state => state.counter)
63+
return <div>{counter}</div>
64+
}
65+
```
66+
67+
Using props to determine what to extract:
68+
69+
```jsx
70+
import React, { useCallback } from 'react'
71+
import { useSelector } from 'react-redux'
72+
73+
export const TodoListItem = props => {
74+
const todoSelector = useCallback(() => {
75+
return state => state.todos[props.id]
76+
}, [props.id])
77+
78+
const todo = useSelector(todoSelector)
79+
80+
return <div>{todo.text}</div>
81+
}
82+
```
83+
84+
## `useActions()`
85+
86+
```js
87+
const boundAC = useActions(actionCreator : Function, deps : any[])
88+
89+
const boundACsObject = useActions(actionCreators : Object<string, Function>, deps : any[])
90+
91+
const boundACsArray = useActions(actionCreators : Function[], deps : any[])
92+
```
93+
94+
Allows you to prepare bound action creators that will dispatch actions to the Redux store when called.
95+
96+
This is conceptually similar to the [`mapDispatchToProps` argument to `connect`](../using-react-redux/connect-dispatching-actions-with-mapDispatchToProps.md). The action creators that are passed in will be bound using the Redux [`bindActionCreators()` utility](https://redux.js.org/api/bindactioncreators), and the bound functions will be returned.
97+
98+
However, there are some differences between the arguments passed to `useActions()` and the `mapDispatch` argument to `connect()`:
99+
100+
- `mapDispatch` may be either a function or an object. `useActions()` accepts a single action creator, an object full of action creators, or an array of action creators, and the return value will be the same form.
101+
- `mapDispatch` is normally used once when the component is instantiated, unless it is a function with the `(dispatch, ownProps)` signature, which causes it to be called any time the props have changed. The action creators passed to `useActions()` will be re-bound (and thus have new function references) whenever the values passed in the `deps` array change. If no `deps` array is provided, the functions will be re-bound every time the component re-renders.
102+
103+
> **Note**: There are potential edge cases with using the object argument form and declaring the object inline. See the [Usage Warnings](#usage-warnings) section of this page for further details.
104+
105+
You may call `useActions()` multiple times in a single component.
106+
107+
#### Examples
108+
109+
```jsx
110+
import React from 'react'
111+
import { useActions } from 'react-redux'
112+
113+
const increaseCounter = ({ amount }) => ({
114+
type: 'increase-counter',
115+
amount
116+
})
117+
118+
export const CounterComponent = ({ value }) => {
119+
// supports passing an object of action creators
120+
const { increaseCounterByOne, increaseCounterByTwo } = useActions(
121+
{
122+
increaseCounterByOne: () => increaseCounter(1),
123+
increaseCounterByTwo: () => increaseCounter(2)
124+
},
125+
[]
126+
)
127+
128+
// supports passing an array/tuple of action creators
129+
const [increaseCounterByThree, increaseCounterByFour] = useActions(
130+
[() => increaseCounter(3), () => increaseCounter(4)],
131+
[]
132+
)
133+
134+
// supports passing a single action creator
135+
const increaseCounterBy5 = useActions(() => increaseCounter(5), [])
136+
137+
// passes through any arguments to the callback
138+
const increaseCounterByX = useActions(x => increaseCounter(x), [])
139+
140+
return (
141+
<div>
142+
<span>{value}</span>
143+
<button onClick={increaseCounterByOne}>Increase counter by 1</button>
144+
</div>
145+
)
146+
}
147+
```
148+
149+
## `useRedux()`
150+
151+
```js
152+
const [selectedValue, boundACs] = useRedux(selector, actionCreators)
153+
```
154+
155+
This hook allows you to both extract values from the Redux store state and bind action creators in a single call. This is conceptually equivalent to the [`connect()` function](./connect.md) accepting both a `mapState` and a `mapDispatch` argument.
156+
157+
`useRedux()` is simply a wrapper for `useSelector()` and `useActions()`, and `useRedux()` passes its arguments directly to them. The return value is an array containing the results of `useSelector()` and `useActions()`, respectively.
158+
159+
Note that `useRedux()` currently does _not_ allow you to specify a dependency array for the `actionCreators` parameter, so they will be re-created every time the component renders. If you need consistent function references, consider using `useActions()` with a dependency array instead.
160+
161+
#### Examples
162+
163+
```jsx
164+
import React from 'react'
165+
import { useRedux } from 'react-redux'
166+
167+
export const CounterComponent = () => {
168+
const [counter, { inc1, inc }] = useRedux(state => state.counter, {
169+
inc1: () => ({ type: 'inc1' }),
170+
inc: amount => ({ type: 'inc', amount })
171+
})
172+
173+
return (
174+
<>
175+
<div>{counter}</div>
176+
<button onClick={inc1}>Increment by 1</button>
177+
<button onClick={() => inc(5)}>Increment by 5</button>
178+
</>
179+
)
180+
}
181+
```
182+
183+
## `useDispatch()`
184+
185+
```js
186+
const dispatch = useDispatch()
187+
```
188+
189+
This hook returns a reference to the `dispatch` function from the Redux store. You may use it to dispatch actions as needed.
190+
191+
#### Examples
192+
193+
```jsx
194+
import React, { useCallback } from 'react'
195+
import { useReduxDispatch } from 'react-redux'
196+
197+
export const CounterComponent = ({ value }) => {
198+
const dispatch = useDispatch()
199+
const increaseCounter = useCallback(
200+
() => dispatch({ type: 'increase-counter' }),
201+
[]
202+
)
203+
204+
return (
205+
<div>
206+
<span>{value}</span>
207+
<button onClick={increaseCounter}>Increase counter</button>
208+
</div>
209+
)
210+
}
211+
```
212+
213+
## `useStore()`
214+
215+
```js
216+
const store = useStore()
217+
```
218+
219+
This hook returns a reference to the same Redux store that was passed in to the `<Provider>` component.
220+
221+
This hook should probably not be used frequently. Prefer `useSelector()` and `useActions()` as your primary choices. However, this may be useful for less common scenarios that do require access to the store, such as replacing reducers.
222+
223+
#### Examples
224+
225+
```jsx
226+
import React from 'react'
227+
import { useStore } from 'react-redux'
228+
229+
export const CounterComponent = ({ value }) => {
230+
const store = useStore()
231+
232+
// EXAMPLE ONLY! Do not do this in a real app.
233+
// The component will not automatically update if the store state changes
234+
return <div>{store.getState()}</div>
235+
}
236+
```
237+
238+
## Usage Warnings
239+
240+
### Stale Props and "Zombie Children"
241+
242+
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.
243+
244+
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.
245+
246+
With hooks, there is no way to render a context provider, which means there's also no nested hierarchy of subscriptions. Because of this, the "stale props" and "zombie child" issues may potentially re-occur in an app that relies on using hooks instead of `connect()`.
247+
248+
Specifically, "stale props" means any case where:
249+
250+
- a selector function relies on this component's props to extract data
251+
- a parent component _would_ re-render and pass down new props as a result of an action
252+
- but this component's selector function executes before this component has had a chance to re-render with those new props
253+
254+
Depending on what props were used and what the current store state is, this _may_ result in incorrect data being returned from the selector, or even an error being thrown.
255+
256+
"Zombie child" refers specifically to the case where:
257+
258+
- Multiple nested connected components are mounted in a first pass, causing a child component to subscribe to the store before its parent
259+
- An action is dispatched that deletes data from the store, such as a todo item
260+
- The parent component _would_ stop rendering that child as a result
261+
- However, because the child subscribed first, its subscription runs before the parent stops rendering it. When it reads a value from the store based on props, that data no longer exists, and if the extraction logic is not careful, this may result in an error being thrown.
262+
263+
Some possible options for avoiding these problems with `useSelector()`:
264+
265+
- Don't rely on props in your selector function for extracting data
266+
- 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`.
267+
- Because connected components add the necessary `Subscription` to the context provider, putting a connected component in the tree just above the components with potential data issues may keep those issues from occurring.
268+
269+
> **Note**: For a longer description of this issue, see [this chat log that describes the problems in more detail](https://gist.github.com/markerikson/faac6ae4aca7b82a058e13216a7888ec), as well as [issue #1179](https://github.com/reduxjs/react-redux/issues/1179).
270+
271+
### Action Object Hoisting
272+
273+
Many developers are used to [using the "object shorthand" form of `mapDispatch`](../using-react-redux/connect-dispatching-actions-with-mapDispatchToProps.md#defining-mapdispatchtoprops-as-an-object) by passing multiple action creators as an inline object argument to `connect()`:
274+
275+
```js
276+
export default connect(
277+
mapState,
278+
{ addTodo, toggleTodo }
279+
)(TodoList)
280+
```
281+
282+
However, this pattern can be problematic when calling `useActions()`. Specifically, the combination of importing action creators by name individually, defining the actions object as an inline argument, _and_ attempting to destructure the results, can lead to hoisting problems that cause errors.
283+
284+
This example shows the problematic pattern:
285+
286+
```js
287+
import { addTodo, toggleTodo } from './todos'
288+
289+
const { addTodo, toggleTodo } = useActions({
290+
addTodo,
291+
toggleTodo
292+
})
293+
```
294+
295+
Due to hoisting, the `addTodo` and `toggleTodo` imports are not used, but instead the declared variables from the const are used in the actions object.
296+
297+
Some options for avoiding this problem:
298+
299+
- Don't destructure the result of `useActions()`. Instead, keep it as a single object (`const actions = useActions()`) and reference them like `actions.addTodo`
300+
- Define the action creators object outside the function component, either by hand (`const actionCreators = {addTodo, toggleTodo}`), or by using the "named imports as an object" syntax (`import * as todoActions from "./todoActions"`).
301+
- Try using the single function or array forms of `useActions()`
302+
303+
> **Note**: for more details on this problem, see [this comment and following in issue #1179](https://github.com/reduxjs/react-redux/issues/1179#issuecomment-482473235), as well as [this codesandbox that demonstrates the issue](https://codesandbox.io/s/7yjn3m9n96).

0 commit comments

Comments
 (0)