useReducer is a React hook for managing complex state logic using a reducer function — a pure function that takes the current state and an action, and returns the new state. It follows the same pattern as Redux but lives inside your component.
How it works:
useReducer(reducer, initialState) which returns [state, dispatch]dispatch(action) to trigger state transitions — the reducer determines the new stateFull code example:
1import { useReducer } from "react";23type Todo = { id: number; text: string; done: boolean };45type State = { todos: Todo[]; filter: "all" | "active" | "completed" };67type Action =8 | { type: "ADD_TODO"; payload: string }9 | { type: "TOGGLE_TODO"; payload: number }10 | { type: "DELETE_TODO"; payload: number }11 | { type: "SET_FILTER"; payload: State["filter"] };1213function reducer(state: State, action: Action): State {14 switch (action.type) {15 case "ADD_TODO":16 return {17 ...state,18 todos: [19 ...state.todos,20 { id: Date.now(), text: action.payload, done: false },21 ],22 };23 case "TOGGLE_TODO":24 return {25 ...state,26 todos: state.todos.map(t =>27 t.id === action.payload ? { ...t, done: !t.done } : t28 ),29 };30 case "DELETE_TODO":31 return {32 ...state,33 todos: state.todos.filter(t => t.id !== action.payload),34 };35 case "SET_FILTER":36 return { ...state, filter: action.payload };37 default:38 return state;39 }40}4142function TodoApp() {43 const [state, dispatch] = useReducer(reducer, {44 todos: [],45 filter: "all",46 });4748 const filteredTodos = state.todos.filter(t => {49 if (state.filter === "active") return !t.done;50 if (state.filter === "completed") return t.done;51 return true;52 });5354 return (55 <div>56 <input57 onKeyDown={e => {58 if (e.key === "Enter") {59 dispatch({ type: "ADD_TODO", payload: e.currentTarget.value });60 e.currentTarget.value = "";61 }62 }}63 placeholder="Add todo..."64 />65 <div>66 {(["all", "active", "completed"] as const).map(f => (67 <button68 key={f}69 onClick={() => dispatch({ type: "SET_FILTER", payload: f })}70 style={{ fontWeight: state.filter === f ? "bold" : "normal" }}71 >72 {f}73 </button>74 ))}75 </div>76 <ul>77 {filteredTodos.map(todo => (78 <li key={todo.id}>79 <span80 onClick={() => dispatch({ type: "TOGGLE_TODO", payload: todo.id })}81 style={{ textDecoration: todo.done ? "line-through" : "none" }}82 >83 {todo.text}84 </span>85 <button onClick={() => dispatch({ type: "DELETE_TODO", payload: todo.id })}>86 X87 </button>88 </li>89 ))}90 </ul>91 </div>92 );93}
When to use useReducer over useState:
When useState is simpler:
Performance note: useReducer and useState have the same performance characteristics — both schedule a re-render. The advantage of useReducer is purely about code organization and predictability, not performance.