What is useReducer?
You know useState inside and out. Now meet its older sibling โ useReducer. If useState is like flipping a light switch, useReducer is like operating a control panel with labeled buttons.
useReducer is a hook that lets you manage state using a reducer function. Instead of calling setState with a value directly, you dispatch an action that describes what happened, and the reducer figures out the new state.
It sounds more complicated than it is. Once you see it in action, you'll wonder why you didn't start using it sooner.
The Reducer Pattern
A reducer is just a function that takes the current state and an action, and returns the next state. Think of it like a factory โ raw materials go in, finished product comes out.
function counterReducer(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:
return state;
}
}
Notice how the reducer never modifies the existing state directly. It always returns a new state object. This keeps your state predictable and makes debugging way easier because you can log every action and see exactly what changed.
Try it Yourself โDispatching Actions
To use useReducer, you call the hook with your reducer function and an initial state. It gives you back the current state and a dispatch function.
function Counter() {
const [state, dispatch] = useReducer(counterReducer, { count: 0 });
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: "INCREMENT" })}>+</button>
<button onClick={() => dispatch({ type: "DECREMENT" })}>-</button>
<button onClick={() => dispatch({ type: "RESET" })}>Reset</button>
</div>
);
}
You don't call the reducer directly. You dispatch actions, and React handles running the reducer and updating state. This separation of concerns is what makes reducers so powerful.
The dispatch function is stable across renders, so you can safely pass it to child components or use it in useEffect without worrying about unnecessary re-renders.
Try it Yourself โuseReducer vs useState
So when should you reach for useReducer instead of useState? Use useState for simple, independent state values โ a toggle, a counter, a text input.
Use useReducer when your state logic starts getting complex. If you have multiple related values that change together, or if your state transitions depend on the previous state in complicated ways, useReducer keeps things organized.
Here's a practical example: a form with multiple fields where validation depends on other fields. With useState you'd end up with a tangled mess of individual setters. With useReducer, each action type clearly describes what changed, and the reducer handles the logic in one place.
Another advantage: when you pair useReducer with context, you get a powerful state management pattern that works great for medium-sized apps without needing external libraries.