useReducer
The useReducer hook is a React hook that lets you add a reducer to your component. It is an alternative to useState, heavily inspired by Redux, and is generally preferred when managing complex state logic that involves multiple sub-values or when the next state depends on the previous one.
Core Concepts
useReducer revolves around three main concepts:
- State: The current state of your component.
- Action: An object describing what happened (usually containing a
typeproperty and an optionalpayload). - Reducer: A pure function that takes the current
stateand anaction, and returns the new state.
Basic Usage
import { useReducer } from 'react';
// 1. Define the reducer function outside the component to avoid recreation
function counterReducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
case 'reset':
return { count: 0 };
case 'set':
return { count: action.payload };
default:
throw new Error(`Unknown action type: ${action.type}`);
}
}
export default function Counter() {
// 2. Initialize useReducer with the reducer and initial state
const [state, dispatch] = useReducer(counterReducer, { count: 0 });
return (
<div>
<p>Count: {state.count}</p>
{/* 3. Dispatch actions to update the state */}
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
<button onClick={() => dispatch({ type: 'set', payload: 100 })}>Set to 100</button>
</div>
);
}
When to use useReducer over useState
While useState is simpler, useReducer shines in the following scenarios:
- Complex State Logic: If your state is an object with multiple nested properties that update together, a reducer keeps the update logic centralized.
- Dependent State Updates: If updating one piece of state requires knowledge of another piece of state (e.g., updating a
loadingstate when fetchingdatasucceeds or fails). - Decoupling Logic from Rendering: Reducers can be written outside the component, making the component cleaner and the state logic easier to unit test independently.
Example: Form Handling
Managing a form with multiple fields and validation states is a classic use case for useReducer:
const initialState = {
username: '',
email: '',
isSubmitting: false,
error: null,
};
function formReducer(state, action) {
switch (action.type) {
case 'input_change':
return { ...state, [action.field]: action.value };
case 'submit_start':
return { ...state, isSubmitting: true, error: null };
case 'submit_success':
return { ...initialState };
case 'submit_error':
return { ...state, isSubmitting: false, error: action.payload };
default:
return state;
}
}