useReducer is a React Hook that provides an alternative to useState for managing complex state logic. It's particularly useful when state transitions depend on the previous state or involve multiple sub-states.
const [state, dispatch] = useReducer(reducer, initialState);- reducer: A function that takes the current state and an action, then returns the new state. Based on the action.type, it returns the new state.
- initialState: The initial value of the state.
- state: holds the current state value
- dispatch: A function used to send actions to the reducer.
const initialState = {count : 0};
function reducer(state, action) {
switch (action.type) {
case "increment":
// Return a NEW object with the updated property
return { ...state, count: state.count + action.payload };
case "decrement":
return { ...state, count: state.count - action.payload };
// ❌ ❌ BAD: Returns a number, not the state object shape! AVOID
// case "increment":
// return state.count + action.payload;
default:
throw new Error("Unknown action type");
}
}function Component(){
const [state, dispatch] = useReducer(reducer, initialState);
const incrementCount = function(){
//sends an action to the reducer, updating the state accordingly.
dispatch({ type: "increment", payload: 1 })
}
const decrementCount = function(){
//sends an action to the reducer, updating the state accordingly
dispatch({ type: "decrement", payload: 1 })
}
return (
<button onClick={incrementCount}>+button>
<span>Count: {state.count}span>
<button onClick={decrementCount}>-button>
)
}📌The
reducermust be pure function and return a completely new state object.
How useReducer Works: The Flow
Here’s the step-by-step process detailing how the components of useReducer interact:
Initial Setup: When your component first renders,
useReduceris called with yourReducer Functionand theinitialState. It returns the currentState Object(which is theinitialStateat first) and theDispatch Function.Event Trigger: Something happens in your application that requires a state change (e.g., a user clicks a button, types in a field, data arrives).
-
Dispatching an Action: The event handler for that event calls the
Dispatch Function. You pass anAction Objecttodispatch. ThisAction Objectcontains information about what change needs to happen (thetype) and any data needed for that change (thepayload).-
Example:
dispatch({ type: 'INCREMENT', payload: 1 })
-
Example:
-
Reducer Called: React takes the
Action Objectyou dispatched and calls yourReducer Function. It provides two arguments to your reducer:- The current
State Object. - The
Action Objectyou just dispatched. -
Example:
reducer(currentState, { type: 'INCREMENT', payload: 1 })
- The current
-
Calculating the New State: Inside your
Reducer Function, you use theaction.type(andaction.payloadif necessary) to determine how to compute the next state based on the current state. This is typically done using aswitchstatement orif/elseconditions based onaction.type. Crucially, the reducer must be pure and return a completely new state object (immutability), not modify the existing one.-
Example: Based on
{ type: 'INCREMENT', payload: 1 }, the reducer might return{ ...currentState, count: currentState.count + 1 }.
-
Example: Based on
State Update:
useReducerreceives the new state object returned by yourReducer Function. It then updates the internal state associated with your component.Re-render: React detects that the state managed by
useReducerhas changed. It schedules your component (and potentially its children) to re-render. When the component re-renders, theuseReducerhook will now return the updatedState Objectfrom step 6. Your UI then displays the information based on this new state.
So, useReducer provides a structured cycle: an action is dispatched describing an intended change -> the reducer function calculates the new state based on the current state and the action -> React updates the state -> the component re-renders with the new state. The dispatch function is the trigger that kicks off this cycle.
When to use useReducer❓
- When state transitions are more complex (if-else chains or multiple related state updates).
- When you need centralized state management.
- You have multiple related state variables.
- State updates are interdependent.
- When the next state depends on the previous state.
- You want to keep your components cleaner and more readable.
Why use useReducer❓
While useState is sufficient for managing simple state, it can become cumbersome when:
- The component has multiple state variables.
- State updates are scattered across multiple event handlers.
- Multiple state updates need to occur simultaneously in response to an event.
- State updates depend on other state variables. In these cases,
useReducerhelps organize state updates in a more structured manner.
🧭 When to Use What?
| Use Case | Recommended Approach |
|---|---|
| Simple local state (e.g. form inputs, toggles) | useState |
| Complex local state (e.g. nested updates, dependencies) | useReducer |
| Shared global state in a small to medium app |
useReducer + Context API |
| Global state in a large, complex application | Redux |
✅ Conclusion
The useReducer hook is a robust alternative to useState, particularly well-suited for managing complex state logic and scenarios where the next state depends on the previous one. By centralizing state transitions within a pure reducer function, useReducer promotes cleaner, more maintainable code and improves the predictability of state changes.
It's an excellent choice when:
- You have multiple related pieces of state.
- State updates are interdependent or complex.
- You want to separate state logic from UI logic for better readability.
Understanding useReducer not only helps structure state management more effectively within components, but also lays a strong foundation for working with more advanced tools like Redux.
Likes, Comments and feedback are welcome!😅
Happy coding!