useReducer() vs useState()
The main difference
| Hook | What for |
|---|---|
useState | Simple, independent state (a single value, short logic) |
useReducer | Complex update logic, many interrelated pieces of data, a clear structure of "actions" |
When it is worth using useReducer()
1. When the update logic is complex or multi-step
If you have several types of changes that depend on different conditions,
it makes more sense to have one centralized reducer instead of many setState calls and if statements.
Example:
const [state, dispatch] = useReducer(reducer, { count: 0, step: 1 });
function reducer(state, action) {
switch (action.type) {
case "increment":
return { ...state, count: state.count + state.step };
case "decrement":
return { ...state, count: state.count - state.step };
case "setStep":
return { ...state, step: action.payload };
default:
return state;
}
}Here useReducer is more convenient than three separate useState calls.
2. When states are interrelated
If changing one field affects others, useReducer lets you
manage them in one place, instead of in dozens of setState calls.
Example:
function formReducer(state, action) {
switch (action.type) {
case "CHANGE_FIELD":
return { ...state, [action.field]: action.value };
case "RESET":
return initialForm;
default:
return state;
}
}Managing a form in one reducer is simpler and more reliable than 5 separate useState calls.
3. When you need predictable behavior and a "Redux approach"
If you want updates to be transparent, testable, and centralized
(for example, like in Redux), useReducer does this with React's built-in tools.
"The reducer makes state deterministic:
the same action → always the same new state."
4. When updates depend on the previous state
Instead of writing a bunch of setState(prev => ...) calls,
the reducer handles this for you - all the logic is already inside reducer().
5. When you want to use context for global state
useReducer is very often combined with useContext to create
global state without Redux / Zustand:
const StoreContext = createContext();
function StoreProvider({ children }) {
const [state, dispatch] = useReducer(reducer, initialState);
return <StoreContext.Provider value={{ state, dispatch }}>{children}</StoreContext.Provider>;
}This is a mini-Redux right inside React: clean, predictable, and with no external dependencies.
When you do NOT need useReducer
| Case | Better to use |
|---|---|
| Simple state (boolean, number, string) | useState |
| Independent states | Several useState calls |
| Logic is short and obvious | useState is simpler and more readable |
| No different "action types" | useReducer is redundant |
Comparison example
With useState
const [count, setCount] = useState(0);
const [step, setStep] = useState(1);
const increment = () => setCount(prev => prev + step);Simple and readable - ideal if the logic is simple.
With useReducer
const [state, dispatch] = useReducer(reducer, { count: 0, step: 1 });
function reducer(state, action) {
switch (action.type) {
case "increment":
return { ...state, count: state.count + state.step };
case "setStep":
return { ...state, step: action.payload };
default:
return state;
}
}More convenient if the logic grows - for example, decrement, reset, double, and so on get added.
Summary
| Criterion | useState | useReducer |
|---|---|---|
| Simple logic | Yes | No |
| Complex interrelations | No | Yes |
| Many types of changes | No | Yes |
| Centralized logic | No | Yes |
| Easy to write | Yes | a bit more code |
| Global state (via context) | possible | ideal |
The main rule:
Use
useStatefor simple states,
anduseReducerwhen state-management logic becomes "mini business logic".
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.