React useReducer
Managing complex state logic with reducers
🔄 What is useReducer?
useReducer is a React Hook for managing complex state logic. It's an alternative to useState when you have multiple state values or complex state transitions that depend on previous state.
import { useReducer } from 'react';
function Counter() {
const [count, dispatch] = useReducer(reducer, 0);
return <button onClick={() => dispatch({ type: 'increment' })}>
Count: {count}
</button>;
}
useReducer Concepts
State
Current state value managed by reducer
const [state, dispatch] = useReducer(reducer, initialState);
Action
Object describing what happened
dispatch({ type: 'increment', payload: 5 });
Reducer
Function that updates state based on action
function reducer(state, action) {
return newState;
}
Dispatch
Function to trigger state updates
dispatch({ type: 'reset' });
🔹 Basic Counter Example
A simple counter demonstrates useReducer fundamentals. The reducer function handles different action types to update the count state accordingly.
import { useReducer } from 'react';
function reducer(state, action) {
switch (action.type) {
case 'increment':
return state + 1;
case 'decrement':
return state - 1;
case 'reset':
return 0;
default:
return state;
}
}
function Counter() {
const [count, dispatch] = useReducer(reducer, 0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
</div>
);
}
Output:
Count: 0
🔹 Complex State Example
useReducer shines when managing objects with multiple properties. This todo list example shows how to handle complex state updates with different actions.
import { useReducer, useState } from 'react';
function todoReducer(state, action) {
switch (action.type) {
case 'add':
return [...state, { id: Date.now(), text: action.text, done: false }];
case 'toggle':
return state.map(todo =>
todo.id === action.id ? { ...todo, done: !todo.done } : todo
);
case 'delete':
return state.filter(todo => todo.id !== action.id);
default:
return state;
}
}
function TodoList() {
const [todos, dispatch] = useReducer(todoReducer, []);
const [text, setText] = useState('');
const handleAdd = () => {
if (text.trim()) {
dispatch({ type: 'add', text });
setText('');
}
};
return (
<div>
<input value={text} onChange={(e) => setText(e.target.value)} />
<button onClick={handleAdd}>Add</button>
<ul>
{todos.map(todo => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.done}
onChange={() => dispatch({ type: 'toggle', id: todo.id })}
/>
<span style={{ textDecoration: todo.done ? 'line-through' : 'none' }}>
{todo.text}
</span>
<button onClick={() => dispatch({ type: 'delete', id: todo.id })}>
Delete
</button>
</li>
))}
</ul>
</div>
);
}
Output:
- Sample todo item
🔹 When to Use useReducer
Choose useReducer over useState in these scenarios:
✅ Use useReducer when:
- Complex state logic: Multiple sub-values or state transitions
- Next state depends on previous: State updates based on current state
- Multiple actions: Many different ways to update state
- Testing: Reducer functions are easy to test independently
❌ Use useState when:
- Simple state: Single primitive values (string, number, boolean)
- Independent updates: State changes don't depend on previous state
- Few updates: Only one or two ways to change state
🔹 useReducer with Payload
Pass additional data with actions using the payload property. This allows you to send values needed for state updates.
function reducer(state, action) {
switch (action.type) {
case 'set_name':
return { ...state, name: action.payload };
case 'set_age':
return { ...state, age: action.payload };
case 'reset':
return { name: '', age: 0 };
default:
return state;
}
}
function UserForm() {
const [user, dispatch] = useReducer(reducer, { name: '', age: 0 });
return (
<div>
<input
value={user.name}
onChange={(e) => dispatch({ type: 'set_name', payload: e.target.value })}
placeholder="Name"
/>
<input
type="number"
value={user.age}
onChange={(e) => dispatch({ type: 'set_age', payload: Number(e.target.value) })}
placeholder="Age"
/>
<button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
<p>Name: {user.name}, Age: {user.age}</p>
</div>
);
}