Advanced Hooks
While useState and useEffect cover 80% of use cases, React provides powerful advanced hooks for managing complex state, performance, and DOM interactions. Mastering these tools distinguishes a junior React developer from a senior one.
1. useReducer: Managing Complex State
When state logic becomes complex (e.g., multiple sub-values, next state depends on previous state), useState can become unwieldy. Enter useReducer.
First Principles: The State Machine
Think of useReducer as a Finite State Machine. Instead of just setting a variable, you send a signal (Action) to a machine (Reducer), and the machine decides what the next state should be based on its current state and the signal.
This decouples what happened (Action) from how state updates (Reducer).
The Pattern
- State: The data source of truth.
- Action: An object describing an event (
{ type: 'INCREMENT' }). - Reducer: A pure function:
(state, action) ⇒ newState.
const [state, dispatch] = useReducer(reducer, initialState);
Interactive: The Reducer Playground
Try dispatching different actions to see how the reducer updates the state. Observe how the logic is centralized.
Dispatch Actions
Current State
{
count: 0,
step: 1
}
2. useContext: Dependency Injection
useContext solves the “prop drilling” problem.
// 1. Create Context
const ThemeContext = React.createContext('light');
// 2. Provide Context
function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
// 3. Consume Context
function Toolbar() {
const theme = useContext(ThemeContext);
return <div className={theme}>...</div>;
}
3. useEffect vs useLayoutEffect
These two hooks are identical in signature but differ in timing.
useEffect: Runs asynchronously after the paint. (99% of use cases).useLayoutEffect: Runs synchronously after DOM mutations but before the paint.
When to use useLayoutEffect?
Use it ONLY when you need to measure the DOM (e.g., width, scroll position) and then update state before the user sees the screen. If you use useEffect for this, the user might see a “flash” of content jumping.
Decision Flowchart
4. useImperativeHandle
Occasionally, a parent component needs to invoke a function directly on a child component (e.g., inputRef.current.focus()). useImperativeHandle allows you to customize what the parent sees.
const FancyInput = forwardRef((props, ref) => {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
shake: () => {
// Custom animation logic
}
}));
return <input ref={inputRef} />;
});
Key Takeaways
useReducer: Predictable state transitions for complex logic.useContext: Global state, but beware of re-renders.useLayoutEffect: Fixes visual flickers when measuring DOM.useImperativeHandle: Exposes custom methods to parents.