|
| 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, deps : any[]) |
| 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 | +- The selector function used will be based on the `deps` array. If no deps array is provided, the latest passed-in selector function will be used when the component renders, and also when any actions are dispatched before the next render. If a deps array is provided, the last saved selector will be used, and that selector will be overwritten whenever the deps array contents have changed. |
| 47 | +- 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. |
| 48 | +- 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()`](https://reactjs.org/docs/hooks-reference.html#usememo) or [`useCallback()`](https://reactjs.org/docs/hooks-reference.html#usecallback) hooks yourself to create a version of the selector that will be re-created whenever the props it depends on change. |
| 49 | + |
| 50 | +> **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. |
| 51 | +
|
| 52 | +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. |
| 53 | + |
| 54 | +#### Examples |
| 55 | + |
| 56 | +Basic usage: |
| 57 | + |
| 58 | +```jsx |
| 59 | +import React from 'react' |
| 60 | +import { useSelector } from 'react-redux' |
| 61 | + |
| 62 | +export const CounterComponent = () => { |
| 63 | + const counter = useSelector(state => state.counter) |
| 64 | + return <div>{counter}</div> |
| 65 | +} |
| 66 | +``` |
| 67 | + |
| 68 | +Using props to determine what to extract: |
| 69 | + |
| 70 | +```jsx |
| 71 | +import React from 'react' |
| 72 | +import { useSelector } from 'react-redux' |
| 73 | + |
| 74 | +export const TodoListItem = props => ( |
| 75 | + const todo = useSelector(state => state.todos[props.id], [props.id]) |
| 76 | + |
| 77 | + return <div>{todo.text}</div> |
| 78 | +} |
| 79 | +``` |
| 80 | +
|
| 81 | +## `useActions()` |
| 82 | +
|
| 83 | +```js |
| 84 | +const boundAC = useActions(actionCreator : Function, deps : any[]) |
| 85 | + |
| 86 | +const boundACsObject = useActions(actionCreators : Object<string, Function>, deps : any[]) |
| 87 | + |
| 88 | +const boundACsArray = useActions(actionCreators : Function[], deps : any[]) |
| 89 | +``` |
| 90 | +
|
| 91 | +Allows you to prepare bound action creators that will dispatch actions to the Redux store when called. |
| 92 | +
|
| 93 | +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. |
| 94 | +
|
| 95 | +However, there are some differences between the arguments passed to `useActions()` and the `mapDispatch` argument to `connect()`: |
| 96 | +
|
| 97 | +- `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. |
| 98 | +- `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. |
| 99 | +
|
| 100 | +> **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. |
| 101 | +
|
| 102 | +You may call `useActions()` multiple times in a single component. |
| 103 | +
|
| 104 | +#### Examples |
| 105 | +
|
| 106 | +```jsx |
| 107 | +import React from 'react' |
| 108 | +import { useActions } from 'react-redux' |
| 109 | + |
| 110 | +const increaseCounter = amount => ({ |
| 111 | + type: 'increase-counter', |
| 112 | + amount |
| 113 | +}) |
| 114 | + |
| 115 | +export const CounterComponent = ({ value }) => { |
| 116 | + // supports passing an object of action creators |
| 117 | + const { increaseCounterByOne, increaseCounterByTwo } = useActions( |
| 118 | + { |
| 119 | + increaseCounterByOne: () => increaseCounter(1), |
| 120 | + increaseCounterByTwo: () => increaseCounter(2) |
| 121 | + }, |
| 122 | + [] |
| 123 | + ) |
| 124 | + |
| 125 | + // supports passing an array/tuple of action creators |
| 126 | + const [increaseCounterByThree, increaseCounterByFour] = useActions( |
| 127 | + [() => increaseCounter(3), () => increaseCounter(4)], |
| 128 | + [] |
| 129 | + ) |
| 130 | + |
| 131 | + // supports passing a single action creator |
| 132 | + const increaseCounterBy5 = useActions(() => increaseCounter(5), []) |
| 133 | + |
| 134 | + // passes through any arguments to the callback |
| 135 | + const increaseCounterByX = useActions(x => increaseCounter(x), []) |
| 136 | + |
| 137 | + return ( |
| 138 | + <div> |
| 139 | + <span>{value}</span> |
| 140 | + <button onClick={increaseCounterByOne}>Increase counter by 1</button> |
| 141 | + </div> |
| 142 | + ) |
| 143 | +} |
| 144 | +``` |
| 145 | +
|
| 146 | +## `useRedux()` |
| 147 | +
|
| 148 | +```js |
| 149 | +const [selectedValue, boundACs] = useRedux(selector, actionCreators) |
| 150 | +``` |
| 151 | +
|
| 152 | +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. |
| 153 | +
|
| 154 | +`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. |
| 155 | +
|
| 156 | +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. |
| 157 | +
|
| 158 | +#### Examples |
| 159 | +
|
| 160 | +```jsx |
| 161 | +import React from 'react' |
| 162 | +import { useRedux } from 'react-redux' |
| 163 | + |
| 164 | +export const CounterComponent = () => { |
| 165 | + const [counter, { inc1, inc }] = useRedux(state => state.counter, { |
| 166 | + inc1: () => ({ type: 'inc1' }), |
| 167 | + inc: amount => ({ type: 'inc', amount }) |
| 168 | + }) |
| 169 | + |
| 170 | + return ( |
| 171 | + <> |
| 172 | + <div>{counter}</div> |
| 173 | + <button onClick={inc1}>Increment by 1</button> |
| 174 | + <button onClick={() => inc(5)}>Increment by 5</button> |
| 175 | + </> |
| 176 | + ) |
| 177 | +} |
| 178 | +``` |
| 179 | +
|
| 180 | +## `useDispatch()` |
| 181 | +
|
| 182 | +```js |
| 183 | +const dispatch = useDispatch() |
| 184 | +``` |
| 185 | +
|
| 186 | +This hook returns a reference to the `dispatch` function from the Redux store. You may use it to dispatch actions as needed. |
| 187 | +
|
| 188 | +#### Examples |
| 189 | +
|
| 190 | +```jsx |
| 191 | +import React, { useCallback } from 'react' |
| 192 | +import { useDispatch } from 'react-redux' |
| 193 | + |
| 194 | +export const CounterComponent = ({ value }) => { |
| 195 | + const dispatch = useDispatch() |
| 196 | + const increaseCounter = useCallback( |
| 197 | + () => dispatch({ type: 'increase-counter' }), |
| 198 | + [] |
| 199 | + ) |
| 200 | + |
| 201 | + return ( |
| 202 | + <div> |
| 203 | + <span>{value}</span> |
| 204 | + <button onClick={increaseCounter}>Increase counter</button> |
| 205 | + </div> |
| 206 | + ) |
| 207 | +} |
| 208 | +``` |
| 209 | +
|
| 210 | +## `useStore()` |
| 211 | +
|
| 212 | +```js |
| 213 | +const store = useStore() |
| 214 | +``` |
| 215 | +
|
| 216 | +This hook returns a reference to the same Redux store that was passed in to the `<Provider>` component. |
| 217 | +
|
| 218 | +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. |
| 219 | +
|
| 220 | +#### Examples |
| 221 | +
|
| 222 | +```jsx |
| 223 | +import React from 'react' |
| 224 | +import { useStore } from 'react-redux' |
| 225 | + |
| 226 | +export const CounterComponent = ({ value }) => { |
| 227 | + const store = useStore() |
| 228 | + |
| 229 | + // EXAMPLE ONLY! Do not do this in a real app. |
| 230 | + // The component will not automatically update if the store state changes |
| 231 | + return <div>{store.getState()}</div> |
| 232 | +} |
| 233 | +``` |
| 234 | +
|
| 235 | +## Usage Warnings |
| 236 | +
|
| 237 | +### Stale Props and "Zombie Children" |
| 238 | +
|
| 239 | +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. |
| 240 | +
|
| 241 | +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. |
| 242 | +
|
| 243 | +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()`. |
| 244 | +
|
| 245 | +Specifically, "stale props" means any case where: |
| 246 | +
|
| 247 | +- a selector function relies on this component's props to extract data |
| 248 | +- a parent component _would_ re-render and pass down new props as a result of an action |
| 249 | +- but this component's selector function executes before this component has had a chance to re-render with those new props |
| 250 | +
|
| 251 | +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. |
| 252 | +
|
| 253 | +"Zombie child" refers specifically to the case where: |
| 254 | +
|
| 255 | +- Multiple nested connected components are mounted in a first pass, causing a child component to subscribe to the store before its parent |
| 256 | +- An action is dispatched that deletes data from the store, such as a todo item |
| 257 | +- The parent component _would_ stop rendering that child as a result |
| 258 | +- 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. |
| 259 | +
|
| 260 | +Some possible options for avoiding these problems with `useSelector()`: |
| 261 | +
|
| 262 | +- Don't rely on props in your selector function for extracting data |
| 263 | +- 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`. |
| 264 | +- 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. |
| 265 | +
|
| 266 | +> **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). |
| 267 | +
|
| 268 | +### Action Object Hoisting |
| 269 | +
|
| 270 | +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()`: |
| 271 | +
|
| 272 | +```js |
| 273 | +export default connect( |
| 274 | + mapState, |
| 275 | + { addTodo, toggleTodo } |
| 276 | +)(TodoList) |
| 277 | +``` |
| 278 | +
|
| 279 | +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. |
| 280 | +
|
| 281 | +This example shows the problematic pattern: |
| 282 | +
|
| 283 | +```js |
| 284 | +import { addTodo, toggleTodo } from './todos' |
| 285 | + |
| 286 | +const { addTodo, toggleTodo } = useActions({ |
| 287 | + addTodo, |
| 288 | + toggleTodo |
| 289 | +}) |
| 290 | +``` |
| 291 | +
|
| 292 | +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. |
| 293 | +
|
| 294 | +Some options for avoiding this problem: |
| 295 | +
|
| 296 | +- Don't destructure the result of `useActions()`. Instead, keep it as a single object (`const actions = useActions()`) and reference them like `actions.addTodo` |
| 297 | +- 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"`). |
| 298 | +- Try using the single function or array forms of `useActions()` |
| 299 | +
|
| 300 | +> **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). |
| 301 | +
|
| 302 | +### Performance |
| 303 | +
|
| 304 | +As mentioned earlier, `useSelector()` will do basic shallow comparisons of return values when running the selector function after an action is dispatched. However, unlike `connect()`, `useSelector()` does not do anything to prevent your own function component from completing a re-render if the derived state has changed. |
| 305 | +
|
| 306 | +If further performance optimizations are necessary, you may consider either wrapping your function component in `React.memo(MyFunctionComponent)`, or using `useMemo()` to memoize the render output of your component: |
| 307 | +
|
| 308 | +```jsx |
| 309 | +// Option 1: use React.memo() to keep the component from re-rendering |
| 310 | + |
| 311 | +const CounterComponent = props => { |
| 312 | + const counter = useSelector(state => state.counter) |
| 313 | + return ( |
| 314 | + <div> |
| 315 | + {props.name}: {counter} |
| 316 | + </div> |
| 317 | + ) |
| 318 | +} |
| 319 | + |
| 320 | +export const MemoizedCounterComponent = React.memo(CounterComponent) |
| 321 | + |
| 322 | +// Option 2: let the component re-render, but memoize output |
| 323 | + |
| 324 | +export const CounterComponent = props => { |
| 325 | + const counter = useSelector(state => state.counter) |
| 326 | + |
| 327 | + const renderedChildren = useMemo(() => { |
| 328 | + return ( |
| 329 | + <div> |
| 330 | + {props.name}: {counter} |
| 331 | + </div> |
| 332 | + ) |
| 333 | + }, [props.name, counter]) |
| 334 | + |
| 335 | + return renderedChildren |
| 336 | +} |
| 337 | +``` |
0 commit comments