Advanced Hooks: useReducer and useRef
45 min`useReducer` is an alternative to `useState` for managing complex state logic. It's useful when you have complex state that involves multiple sub-values.
`useRef` returns a mutable ref object whose `.current` property is initialized to the passed argument. It's commonly used to access DOM elements directly.
These hooks provide more control over state management and DOM interactions.
useReducer follows the same pattern as Redux reducers, making it familiar if you've worked with Redux. It's ideal when state updates depend on previous state or involve complex logic.
useRef doesn't trigger re-renders when its value changes, making it perfect for storing mutable values that don't need to cause renders, like timers or previous values.
Refs persist across renders but don't cause re-renders, unlike state. This makes them useful for storing values that need to persist but shouldn't trigger updates.
Key Concepts
- useReducer manages complex state with reducer functions.
- useRef provides mutable references that don't trigger re-renders.
- useReducer is ideal for complex state logic with multiple sub-values.
- useRef is commonly used to access DOM elements directly.
- Refs persist across renders without causing re-renders.
Learning Objectives
Master
- Using useReducer for complex state management
- Understanding reducer functions and action patterns
- Using useRef to access DOM elements
- Storing mutable values that don't need re-renders
Develop
- State management architecture thinking
- Understanding when to use useReducer vs useState
- Recognizing use cases for refs vs state
Tips
- Use useReducer when state logic is complex or involves multiple sub-values.
- Use useRef for DOM access or storing values that don't need re-renders.
- Don't read or write refs during rendering - use them in effects or event handlers.
- Consider useReducer when you have complex state update logic.
Common Pitfalls
- Using useReducer for simple state that could use useState.
- Mutating ref.current during rendering (should be in effects/handlers).
- Using refs when state would be more appropriate.
- Forgetting that refs don't cause re-renders when changed.
Summary
- useReducer manages complex state with reducer functions.
- useRef provides mutable references without re-renders.
- useReducer is ideal for complex state update logic.
- useRef is perfect for DOM access and persistent mutable values.
Exercise
Create a todo list using useReducer for state management.
import { useReducer } from 'react';
const todoReducer = (state, action) => {
switch (action.type) {
case 'ADD_TODO':
return [...state, { id: Date.now(), text: action.text, completed: false }];
case 'TOGGLE_TODO':
return state.map(todo =>
todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
);
default:
return state;
}
};
function TodoList() {
const [todos, dispatch] = useReducer(todoReducer, []);
const [input, setInput] = useState('');
const addTodo = () => {
if (input.trim()) {
dispatch({ type: 'ADD_TODO', text: input });
setInput('');
}
};
return (
<div>
<input value={input} onChange={(e) => setInput(e.target.value)} />
<button onClick={addTodo}>Add Todo</button>
<ul>
{todos.map(todo => (
<li key={todo.id} onClick={() => dispatch({ type: 'TOGGLE_TODO', id: todo.id })}>
{todo.completed ? <s>{todo.text}</s> : todo.text}
</li>
))}
</ul>
</div>
);
}
Exercise Tips
- Add a DELETE_TODO action to the reducer.
- Use useRef to focus the input after adding a todo.
- Consider adding local storage persistence.
- Extract action types as constants for better maintainability.