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
reducer
must 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,
useReducer
is called with yourReducer Function
and theinitialState
. It returns the currentState Object
(which is theinitialState
at 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 Object
todispatch
. ThisAction Object
contains 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 Object
you dispatched and calls yourReducer Function
. It provides two arguments to your reducer:- The current
State Object
. - The
Action Object
you just dispatched. -
Example:
reducer(currentState, { type: 'INCREMENT', payload: 1 })
- The current
-
Calculating the New State: Inside your
Reducer Function
, you use theaction.type
(andaction.payload
if necessary) to determine how to compute the next state based on the current state. This is typically done using aswitch
statement orif/else
conditions 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:
useReducer
receives 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
useReducer
has changed. It schedules your component (and potentially its children) to re-render. When the component re-renders, theuseReducer
hook will now return the updatedState Object
from 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,
useReducer
helps 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!