React useReducer Hook
The React useReducer hook is an alternative to useState for state with many related values or many ways to change it. Instead of calling setters all over your component, you dispatch actions that describe what happened, and a single reducer function decides how the state changes.
useReducer Syntax
A reducer is a pure function that takes the current state and an action and returns the new state: (state, action) => newState.
const [state, dispatch] = useReducer(reducer, initialState);state -> current state dispatch -> function you call with an action
A Counter with useReducer
import { useReducer } from 'react';
function reducer(state, action) {
switch (action.type) {
case 'increment': return { count: state.count + 1 };
case 'decrement': return { count: state.count - 1 };
case 'reset': return { count: 0 };
default: throw new Error('Unknown action: ' + action.type);
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
</>
);
}Count: 0 (+ + + -) Count: 2
A Shopping Cart Reducer
Actions can carry extra data, often in a payload field. Notice that the reducer never mutates state; it always returns new arrays and objects.
function cartReducer(cart, action) {
switch (action.type) {
case 'add': {
const found = cart.find((i) => i.id === action.item.id);
if (found) {
return cart.map((i) => (i.id === found.id ? { ...i, qty: i.qty + 1 } : i));
}
return [...cart, { ...action.item, qty: 1 }];
}
case 'remove':
return cart.filter((i) => i.id !== action.id);
case 'clear':
return [];
default:
return cart;
}
}
// dispatch({ type: 'add', item: { id: 7, name: 'Keyboard', price: 49 } });[{ id: 7, name: 'Keyboard', price: 49, qty: 1 }]useReducer vs useState
- useState: simple, independent values like a toggle or an input string.
- useReducer: several values that change together, or many different update types.
- Reducers are easy to test because they are plain functions with no React code.
- Combine useReducer with Context to share state and dispatch across the app.
Frequently asked questions
When should I use useReducer instead of useState?
Use useReducer when the next state depends on complex logic, when several state values update together, or when you have many event handlers that change the same state.
Is useReducer the same as Redux?
They share the reducer and action idea, but useReducer is local to a component. Redux keeps one global store with middleware and devtools.
Can a reducer make API calls?
No. Reducers must be pure and synchronous. Make API calls in event handlers or effects, then dispatch actions with the results.