useReducer Hook
In this page:
Writing a Reducer Function
A reducer is a plain function that takes the current state and an action, then returns the new state -- similar to how Array.reduce works. It centralizes all the logic for how state changes in one place, rather than scattering setter calls throughout the component.
Note: A reducer should always return a brand-new state object/value, never mutate the one it received.
Warning: Forgetting a default: return state; case means dispatching an unrecognized action type silently does nothing, which can be confusing to debug.
Example: Writing a Reducer Function
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function counterReducer(state, action) {
switch (action.type) {
case "increment": return state + 1;
case "decrement": return state - 1;
default: return state;
}
}
function Counter() {
const [count, dispatch] = React.useReducer(counterReducer, 0);
return (
<div>
<p>{count}</p>
<button onClick={() => dispatch({ type: "increment" })}>+</button>
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<Counter />);
</script>
</body>
</html>
Dispatching Actions
Instead of calling a setter function directly, useReducer gives you a dispatch function -- you call it with an action object describing what happened, and the reducer decides how state should change in response. This separates 'what happened' from 'how state changes because of it'.
Note: Action objects conventionally have a type field describing the event, plus any extra data the reducer needs (a payload).
Warning: Dispatch does not directly set state -- it's the reducer function that computes the actual new state from the action.
Example: Dispatching Actions
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function reducer(state, action) {
switch (action.type) {
case "setName": return { ...state, name: action.payload };
default: return state;
}
}
function NameForm() {
const [state, dispatch] = React.useReducer(reducer, { name: "" });
return (
<div>
<input onChange={(e) => dispatch({ type: "setName", payload: e.target.value })} />
<p>Name: {state.name}</p>
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<NameForm />);
</script>
</body>
</html>
When to Choose useReducer over useState
useReducer shines when a component's state involves several related values that change together, or when the next state depends on complex logic based on the previous state. For simple, independent values, useState remains simpler and more direct.
Note: If you find a component's state updates involve a lot of interconnected if logic, that's a signal useReducer might organize it better than several useState calls.
Warning: Reaching for useReducer for a single boolean toggle or simple counter adds unnecessary complexity compared to a plain useState.
Example: When to Choose useReducer over useState
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function formReducer(state, action) {
switch (action.type) {
case "updateField": return { ...state, [action.field]: action.value };
case "reset": return { name: "", email: "" };
default: return state;
}
}
function SignupForm() {
const [state, dispatch] = React.useReducer(formReducer, { name: "", email: "" });
return (
<div>
<input placeholder="Name" onChange={(e) => dispatch({ type: "updateField", field: "name", value: e.target.value })} />
<p>Name: {state.name}</p>
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<SignupForm />);
</script>
</body>
</html>
- Writing a reducer function that mutates the existing state instead of returning a brand-new state object.
- Forgetting to include a
defaultcase in the reducer's switch statement, silently ignoring unknown actions. - Reaching for useReducer for very simple state that a plain useState would handle just as well.
- useReducer is an alternative to useState for managing more complex state logic.
- It takes a reducer function and an initial state, returning the current state and a dispatch function.
- You update state by dispatching action objects, not by calling a setter directly.
- It's especially useful when state updates depend on multiple related pieces of logic.
Available since React 16.8, when Hooks were introduced.
Chapter Quiz — Complete all 12 topics to unlock
0/12 topics done
Complete these topics first: