When to Optimize
useMemo, useCallback, and React.memo are optimization tools. Like any tool, using them incorrectly causes more problems than they solve. This lesson gives you a framework for deciding when optimization is worth it.
The cost of optimization
Section titled “The cost of optimization”Every optimization hook has real costs:
- Memory: Cached values are held in memory even when you do not need them.
- Comparison overhead: Every render compares dependencies (for
useMemo/useCallback) or props (forReact.memo). - Complexity: More hooks make code harder to read and debug.
- False confidence: Incorrect dependencies can cause stale data bugs that are hard to trace.
These costs are worth paying when the optimization is measurably faster. They are not worth paying by default.
What fast actually means
Section titled “What fast actually means”React renders are measured in milliseconds. The browser paints at 60fps — you have 16.6ms per frame. A render that takes 0.1ms re-running a reduce on a 10-item array saves essentially nothing by memoizing. The comparison overhead of useMemo might take longer than the computation it avoids.
Optimization becomes relevant when:
- A render takes more than ~2ms (measure in React DevTools Profiler)
- A large list (hundreds or thousands of items) re-renders every time
- An expensive computation (complex data transformation, sorting a large dataset) runs on every render
The right workflow
Section titled “The right workflow”- Write the code without optimization. Make it correct first.
- Measure if it feels slow. Use the React DevTools Profiler to see which components render and how long they take.
- Optimize the bottleneck. Apply
useMemo,useCallback, andReact.memoto the specific component or calculation that is slow — not everywhere. - Verify the optimization worked. Profile again. If the numbers are the same, remove the optimization.
Common over-optimization patterns
Section titled “Common over-optimization patterns”Wrapping every handler in useCallback:
const handleClick = useCallback(() => setCount(c => c + 1), []);If handleClick is not passed to a memoized component and not used as a useEffect dependency, this does nothing useful.
Wrapping every calculation in useMemo:
const doubled = useMemo(() => count * 2, [count]);count * 2 is a single multiplication. useMemo costs more than the computation.
Adding React.memo to every component:
If props change on every render anyway (because they are new objects), React.memo just adds comparison overhead without preventing any renders.
When ZeroBudget needs it
Section titled “When ZeroBudget needs it”ZeroBudget uses optimization judiciously:
spentByCategoryusesuseMemo— it iterates all transactions and the result is passed to everyCategoryCard.totalIncomeandtotalBudgeteduseuseMemo— they feed theleftToAssigncalculation.- Action callbacks use
useCallback— they are passed toCategoryCard, which usesReact.memo. CategoryCardusesReact.memo— 8 cards re-rendering on every state change is measurable overhead.
Everything else renders without memoization. The income section, the transaction form, and the month nav are fine without it — they are small, fast, and do not re-render frequently.
Exercise
Section titled “Exercise”- Open React DevTools in your browser. Enable the Profiler and record a session where you add an income source.
- Look at which components re-rendered and how long each took.
- Identify one component that re-rendered unnecessarily (it received the same props as before).
- Apply the appropriate optimization (
React.memo,useCallback, oruseMemo) and profile again to confirm the improvement.
useMemo,useCallback, andReact.memohave real costs — use them where you can measure the benefit.- Write correct code first. Profile. Optimize bottlenecks. Profile again.
- Do not optimize small calculations, rarely-rendered components, or handlers that are not passed to memoized children.
- The memoization triad works together:
React.memoon the component,useCallbackfor functions,useMemofor objects.