useReducer Hook
useReducer = useState for complex state logic
What is useReducer?
"State management with reducer pattern"
useReducer is a React Hook for managing complex state logic using a reducer function.
"useReducer is a React Hook that manages state using a reducer function, similar to Redux, useful for complex state logic."
Basic Syntax
useReducer(reducer, initialState)
const [state, dispatch] = useReducer(reducer, initialState);
Returns current state and dispatch function.
"useReducer takes a reducer function and initial state, returning the current state and a dispatch function."
Reducer Function
(state, action) => newState
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:
return state;
}
}
"The reducer function takes current state and an action, returning the new state based on the action type."
Dispatch Actions
dispatch with action object
const [state, dispatch] = useReducer(reducer, { count: 0 });
dispatch({ type: "increment" });
dispatch({ type: "decrement" });
dispatch({ type: "reset" });
"Dispatch sends actions to the reducer, which updates state based on the action type."
When to Use useReducer
Complex state · Multiple sub-values · Predictable updates
Use when:
- Complex state logic
- State has multiple sub-values
- Next state depends on previous
- Need predictable state updates
"Use useReducer for complex state logic, multiple related state values, or when state updates follow predictable patterns."
useReducer vs useState
useReducer = complex, useState = simple
| Scenario | Hook |
|---|---|
| Simple state | useState |
| Complex logic | useReducer |
| Multiple values | useReducer |
| Predictable updates | useReducer |
"Use useState for simple state, useReducer for complex state logic or multiple related values."
Lazy Initialization
Function initializer
function init(initialCount) {
return { count: initialCount };
}
const [state, dispatch] = useReducer(reducer, initialCount, init);
"useReducer supports lazy initialization with a third parameter function for expensive initial state setup."
Action Payloads
Actions can carry data
function reducer(state, action) {
switch (action.type) {
case "add":
return { items: [...state.items, action.payload] };
case "remove":
return { items: state.items.filter((i) => i.id !== action.payload) };
default:
return state;
}
}
dispatch({ type: "add", payload: newItem });
"Actions can include payload data for more complex state updates."
9️⃣ Common Pattern: Todo List
Classic useReducer example
function todoReducer(state, action) {
switch (action.type) {
case "add":
return [...state, action.payload];
case "toggle":
return state.map((todo) =>
todo.id === action.payload
? { ...todo, completed: !todo.completed }
: todo,
);
case "delete":
return state.filter((todo) => todo.id !== action.payload);
default:
return state;
}
}
"Todo lists are a common useReducer pattern, managing arrays of items with add, toggle, and delete actions."
Best Practices
✅ Keep reducers pure (no side effects) ✅ Use action types as constants ✅ Handle all action types ✅ Return state for unknown actions ✅ Use TypeScript for type safety ❌ Don't mutate state directly
"useReducer manages complex state using a reducer function that takes state and action, returning new state. It's useful for complex logic, multiple related values, and predictable updates. Dispatch sends actions to the reducer. Prefer useReducer over useState when state logic is complex or follows predictable patterns."
🧠 Ultra-Short Cheat Sheet
Complex state management
Reducer pattern
(state, action) => newState
dispatch actions
Better than useState for complex logic
Lazy initialization
Action payloads
Pure functions