---
title: useReducer
---
`useReducer` is a React Hook that lets you add a [reducer](/learn/extracting-state-logic-into-a-reducer) to your component.
```js
const [state, dispatch] = useReducer(reducer, initialArg, init?)
```
---
## Reference {/*reference*/}
### `useReducer(reducer, initialArg, init?)` {/*usereducer*/}
Call `useReducer` at the top level of your component to manage its state with a [reducer.](/learn/extracting-state-logic-into-a-reducer)
```js
import { useReducer } from 'react';
function reducer(state, action) {
// ...
}
function MyComponent() {
const [state, dispatch] = useReducer(reducer, { age: 42 });
// ...
```
[See more examples below.](#usage)
#### Parameters {/*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 next `init` argument.
* **optional** `init`: The initializer function that should return the initial state. If it's not specified, the initial state is set to `initialArg`. Otherwise, the initial state is set to the result of calling `init(initialArg)`.
#### Returns {/*returns*/}
`useReducer` returns an array with exactly two values:
1. The current state. During the first render, it's set to `init(initialArg)` or `initialArg` (if there's no `init`).
2. The [`dispatch` function](#dispatch) that lets you update the state to a different value and trigger a re-render.
#### Caveats {/*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.](#my-reducer-or-initializer-function-runs-twice) 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 {/*dispatch*/}
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:
```js
const [state, dispatch] = useReducer(reducer, { age: 42 });
function handleClick() {
dispatch({ type: 'incremented_age' });
// ...
```
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 {/*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 a `type` property identifying it and, optionally, other properties with additional information.
#### Returns {/*dispatch-returns*/}
`dispatch` functions do not have a return value.
#### Caveats {/*setstate-caveats*/}
* The `dispatch` function **only updates the state variable for the *next* render**. If you read the state variable after calling the `dispatch` function, [you will still get the old value](#ive-dispatched-an-action-but-logging-gives-me-the-old-state-value) that was on the screen before your call.
* If the new value you provide is identical to the current `state`, as determined by an [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/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.](/learn/queueing-a-series-of-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 use [`flushSync`.](/reference/react-dom/flushSync)
---
## Usage {/*usage*/}
### Adding a reducer to a component {/*adding-a-reducer-to-a-component*/}
Call `useReducer` at the top level of your component to manage state with a [reducer.](/learn/extracting-state-logic-into-a-reducer)
```js [[1, 8, "state"], [2, 8, "dispatch"], [4, 8, "reducer"], [3, 8, "{ age: 42 }"]]
import { useReducer } from 'react';
function reducer(state, action) {
// ...
}
function MyComponent() {
const [state, dispatch] = useReducer(reducer, { age: 42 });
// ...
```
`useReducer` returns an array with exactly two items:
1. The current state of this state variable, initially set to the initial state you provided.
2. The `dispatch` function that lets you change it in response to interaction.
To update what's on the screen, call `dispatch` with an object representing what the user did, called an *action*:
```js [[2, 2, "dispatch"]]
function handleClick() {
dispatch({ type: 'incremented_age' });
}
```
React will pass the current state and the action to your reducer function. Your reducer will calculate and return the next state. React will store that next state, render your component with it, and update the UI.
```js
import { useReducer } from 'react';
function reducer(state, action) {
if (action.type === 'incremented_age') {
return {
age: state.age + 1
};
}
throw Error('Unknown action.');
}
export default function Counter() {
const [state, dispatch] = useReducer(reducer, { age: 42 });
return (
<>
);
}
function Task({ task, onChange, onDelete }) {
const [isEditing, setIsEditing] = useState(false);
let taskContent;
if (isEditing) {
taskContent = (
<>
{
onChange({
...task,
text: e.target.value
});
}} />
>
);
} else {
taskContent = (
<>
{task.text}
>
);
}
return (
);
}
```
```css
button { margin: 5px; }
li { list-style-type: none; }
ul, li { margin: 0; padding: 0; }
```
```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"
}
}
```
---
### Avoiding recreating the initial state {/*avoiding-recreating-the-initial-state*/}
React saves the initial state once and ignores it on the next renders.
```js
function createInitialState(username) {
// ...
}
function TodoList({ username }) {
const [state, dispatch] = useReducer(reducer, createInitialState(username));
// ...
```
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:
```js {6}
function createInitialState(username) {
// ...
}
function TodoList({ username }) {
const [state, dispatch] = useReducer(reducer, username, createInitialState);
// ...
```
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 {/*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.
```js App.js hidden
import TodoList from './TodoList.js';
export default function App() {
return ;
}
```
```js TodoList.js active
import { useReducer } from 'react';
function createInitialState(username) {
const initialTodos = [];
for (let i = 0; i < 50; i++) {
initialTodos.push({
id: i,
text: username + "'s task #" + (i + 1)
});
}
return {
draft: '',
todos: initialTodos,
};
}
function reducer(state, action) {
switch (action.type) {
case 'changed_draft': {
return {
draft: action.nextDraft,
todos: state.todos,
};
};
case 'added_todo': {
return {
draft: '',
todos: [{
id: state.todos.length,
text: state.draft
}, ...state.todos]
}
}
}
throw Error('Unknown action: ' + action.type);
}
export default function TodoList({ username }) {
const [state, dispatch] = useReducer(
reducer,
username,
createInitialState
);
return (
<>
{
dispatch({
type: 'changed_draft',
nextDraft: e.target.value
})
}}
/>
{state.todos.map(item => (
{item.text}
))}
>
);
}
```
#### Passing the initial state directly {/*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.
```js App.js hidden
import TodoList from './TodoList.js';
export default function App() {
return ;
}
```
```js TodoList.js active
import { useReducer } from 'react';
function createInitialState(username) {
const initialTodos = [];
for (let i = 0; i < 50; i++) {
initialTodos.push({
id: i,
text: username + "'s task #" + (i + 1)
});
}
return {
draft: '',
todos: initialTodos,
};
}
function reducer(state, action) {
switch (action.type) {
case 'changed_draft': {
return {
draft: action.nextDraft,
todos: state.todos,
};
};
case 'added_todo': {
return {
draft: '',
todos: [{
id: state.todos.length,
text: state.draft
}, ...state.todos]
}
}
}
throw Error('Unknown action: ' + action.type);
}
export default function TodoList({ username }) {
const [state, dispatch] = useReducer(
reducer,
createInitialState(username)
);
return (
<>
{
dispatch({
type: 'changed_draft',
nextDraft: e.target.value
})
}}
/>