Mastering React useReducer: Centralizing Complex State
While useState is perfect for simple values, useReducer is the go-to tool for state that involves complex logic, multiple sub-values, or when the next state depends heavily on the previous state. It follows the same principles as Redux, making state transitions highly structured and easier to debug.
1. The Core Components
useReducer revolves around three main concepts:
State: The current data.
Dispatch: A function that sends an “action” command to the reducer.
Reducer: A pure function that takes the current
stateand anaction, then returns the new state.
const [state, dispatch] = useReducer(reducer, initialState);2. Defining the Reducer
The reducer function uses switch statements to determine how the state should change based on the action type.
function reducer(state, action){
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
case 'DECREMENT':
return { ...state, count: state.count - 1 };
default:
return state; // Always handle the default case
}
}3. Real-World Scenario: Shopping Cart
useReducer is ideal for managing multiple related state properties, such as a shopping cart containing products and a total price.
function basketReducer(state, action) {
switch (action.type) {
case 'ADD':
// Complex logic: Check if product exists, then update or add
return { ...state, items: [...state.items, action.payload] };
case 'REMOVE':
return { ...state, items: state.items.filter(item => item.id !== action.payload) };
case 'CLEAR':
return initialState;
default:
return state;
}
}
// Usage in component:
dispatch({ type: 'ADD', payload: product });Essential Best Practices & Pitfalls
Never Mutate State: Just like
useState, you must never modify the state object directly (e.g.,state.list.push()). Always return a new object using the spread operator (...).Handle Default Case: Always include a
defaultcase in your reducer to return the current state, preventing bugs when an unknown action type is dispatched.Define Outside Component: For cleaner code, define your reducer function outside of the component body.
Avoid Over-Engineering: Do not use
useReducerfor simple values like a single boolean toggle;useStateis cleaner for those scenarios.
Conclusion
useReducer turns your state management into a predictable, testable, and centralized system. By shifting from "what the value should be" to "what action should happen," you create a more maintainable code structure especially as your React applications scale in complexity.