Use Reducer π§
OverviewΒΆ
useReducer
is a React Hook that lets you add a reducer to your component.
1 |
|
ReferenceΒΆ
useReducer(reducer, initialArg, init?)
ΒΆ
Call useReducer
at the top level of your component to manage its state with a reducer.
1 2 3 4 5 6 7 8 9 |
|
ParametersΒΆ
reducer
: The reducer function that specifies how the state gets updated. It must be pure, should take the state and action as arguments, and should return the next state. State and action can be of any types.initialArg
: The value from which the initial state is calculated. It can be a value of any type. How the initial state is calculated from it depends on the nextinit
argument.- optional
init
: The initializer function that should return the initial state. If it's not specified, the initial state is set toinitialArg
. Otherwise, the initial state is set to the result of callinginit(initialArg)
.
ReturnsΒΆ
useReducer
returns an array with exactly two values:
- The current state. During the first render, it's set to
init(initialArg)
orinitialArg
(if there's noinit
). - The
dispatch
function that lets you update the state to a different value and trigger a re-render.
CaveatsΒΆ
useReducer
is a Hook, so you can only call it at the top level of your component or your own Hooks. You can't call it inside loops or conditions. If you need that, extract a new component and move the state into it.- In Strict Mode, React will call your reducer and initializer twice in order to help you find accidental impurities. This is development-only behavior and does not affect production. If your reducer and initializer are pure (as they should be), this should not affect your logic. The result from one of the calls is ignored.
dispatch
functionΒΆ
The dispatch
function returned by useReducer
lets you update the state to a different value and trigger a re-render. You need to pass the action as the only argument to the dispatch
function:
1 2 3 4 5 |
|
React will set the next state to the result of calling the reducer
function you've provided with the current state
and the action you've passed to dispatch
.
ParametersΒΆ
action
: The action performed by the user. It can be a value of any type. By convention, an action is usually an object with atype
property identifying it and, optionally, other properties with additional information.
ReturnsΒΆ
dispatch
functions do not have a return value.
CaveatsΒΆ
-
The
dispatch
function only updates the state variable for the next render. If you read the state variable after calling thedispatch
function, you will still get the old value that was on the screen before your call. -
If the new value you provide is identical to the current
state
, as determined by anObject.is
comparison, React will skip re-rendering the component and its children. This is an optimization. React may still need to call your component before ignoring the result, but it shouldn't affect your code. -
React batches state updates. It updates the screen after all the event handlers have run and have called their
set
functions. This prevents multiple re-renders during a single event. In the rare case that you need to force React to update the screen earlier, for example to access the DOM, you can useflushSync
.
UsageΒΆ
Adding a reducer to a componentΒΆ
Call useReducer
at the top level of your component to manage state with a reducer.
1 2 3 4 5 6 7 8 9 |
|
useReducer
returns an array with exactly two items:
- The
current state of this state variable, initially set to theinitial state you provided. - The
that lets you change it in response to interaction.dispatch
function
To update what's on the screen, call dispatch
1 2 3 |
|
React will pass the current state and the action to your
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
|
1 2 3 4 |
|
useReducer
is very similar to useState
, but it lets you move the state update logic from event handlers into a single function outside of your component. Read more about choosing between useState
and useReducer
.
Writing the reducer functionΒΆ
A reducer function is declared like this:
1 2 3 |
|
Then you need to fill in the code that will calculate and return the next state. By convention, it is common to write it as a switch
statement. For each case
in the switch
, calculate and return some next state.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Actions can have any shape. By convention, it's common to pass objects with a type
property identifying the action. It should include the minimal necessary information that the reducer needs to compute the next state.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
The action type names are local to your component. Each action describes a single interaction, even if that leads to multiple changes in data. The shape of the state is arbitrary, but usually it'll be an object or an array.
Read extracting state logic into a reducer to learn more.
State is read-only. Don't modify any objects or arrays in state:
1 2 3 4 5 6 7 |
|
Instead, always return new objects from your reducer:
1 2 3 4 5 6 7 8 9 |
|
Read updating objects in state and updating arrays in state to learn more.
Form (object)ΒΆ
In this example, the reducer manages a state object with two fields: name
and age
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
|
1 2 3 4 |
|
Todo list (array)ΒΆ
In this example, the reducer manages an array of tasks. The array needs to be updated without mutation.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
|
1 2 3 4 5 6 7 8 9 10 11 |
|
Writing concise update logic with ImmerΒΆ
If updating arrays and objects without mutation feels tedious, you can use a library like Immer to reduce repetitive code. Immer lets you write concise code as if you were mutating objects, but under the hood it performs immutable updates:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
|
1 2 3 4 5 6 7 8 9 10 11 |
|
```json package.json { "dependencies": { "immer": "1.7.3", "react": "latest", "react-dom": "latest", "react-scripts": "latest", "use-immer": "0.5.1" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test --env=jsdom", "eject": "react-scripts eject" } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Although the result of createInitialState(username)
is only used for the initial render, you're still calling this function on every render. This can be wasteful if it's creating large arrays or performing expensive calculations.
To solve this, you may pass it as an initializer function to useReducer
as the third argument instead:
1 2 3 4 5 6 7 |
|
Notice that youβre passing createInitialState
, which is the function itself, and not createInitialState()
, which is the result of calling it. This way, the initial state does not get re-created after initialization.
In the above example, createInitialState
takes a username
argument. If your initializer doesn't need any information to compute the initial state, you may pass null
as the second argument to useReducer
.
Passing the initializer functionΒΆ
This example passes the initializer function, so the createInitialState
function only runs during initialization. It does not run when component re-renders, such as when you type into the input.
1 2 3 4 5 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
|
Passing the initial state directlyΒΆ
This example does not pass the initializer function, so the createInitialState
function runs on every render, such as when you type into the input. There is no observable difference in behavior, but this code is less efficient.
1 2 3 4 5 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
|
TroubleshootingΒΆ
I've dispatched an action, but logging gives me the old state valueΒΆ
Calling the dispatch
function does not change state in the running code:
1 2 3 4 5 6 7 8 9 10 |
|
This is because states behaves like a snapshot. Updating state requests another render with the new state value, but does not affect the state
JavaScript variable in your already-running event handler.
If you need to guess the next state value, you can calculate it manually by calling the reducer yourself:
1 2 3 4 5 6 |
|
I've dispatched an action, but the screen doesn't updateΒΆ
React will ignore your update if the next state is equal to the previous state, as determined by an Object.is
comparison. This usually happens when you change an object or an array in state directly:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
You mutated an existing state
object and returned it, so React ignored the update. To fix this, you need to ensure that you're always updating objects in state and updating arrays in state instead of mutating them:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
A part of my reducer state becomes undefined after dispatchingΒΆ
Make sure that every case
branch copies all of the existing fields when returning the new state:
1 2 3 4 5 6 7 8 9 |
|
Without ...state
above, the returned next state would only contain the age
field and nothing else.
My entire reducer state becomes undefined after dispatchingΒΆ
If your state unexpectedly becomes undefined
, you're likely forgetting to return
state in one of the cases, or your action type doesn't match any of the case
statements. To find why, throw an error outside the switch
:
1 2 3 4 5 6 7 8 9 10 11 |
|
You can also use a static type checker like TypeScript to catch such mistakes.
I'm getting an error: "Too many re-renders"ΒΆ
You might get an error that says: Too many re-renders. React limits the number of renders to prevent an infinite loop.
Typically, this means that you're unconditionally dispatching an action during render, so your component enters a loop: render, dispatch (which causes a render), render, dispatch (which causes a render), and so on. Very often, this is caused by a mistake in specifying an event handler:
1 2 3 4 5 6 7 8 |
|
If you can't find the cause of this error, click on the arrow next to the error in the console and look through the JavaScript stack to find the specific dispatch
function call responsible for the error.
My reducer or initializer function runs twiceΒΆ
In Strict Mode, React will call your reducer and initializer functions twice. This shouldn't break your code.
This development-only behavior helps you keep components pure. React uses the result of one of the calls, and ignores the result of the other call. As long as your component, initializer, and reducer functions are pure, this shouldn't affect your logic. However, if they are accidentally impure, this helps you notice the mistakes.
For example, this impure reducer function mutates an array in state:
1 2 3 4 5 6 7 8 9 10 |
|
Because React calls your reducer function twice, you'll see the todo was added twice, so you'll know that there is a mistake. In this example, you can fix the mistake by replacing the array instead of mutating it:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Now that this reducer function is pure, calling it an extra time doesn't make a difference in behavior. This is why React calling it twice helps you find mistakes. Only component, initializer, and reducer functions need to be pure. Event handlers don't need to be pure, so React will never call your event handlers twice.
Read keeping components pure to learn more.