Part 7.4 — Performance Hooks: useMemo, useCallback & useDeferredValue
Performance problems in React rarely come from missing useMemo.
They come from unclear boundaries, unnecessary re-renders, and incorrect state placement.
Performance hooks are tools of last resort—not default patterns. This post shows you how to reason about them calmly and apply them only when they provide measurable value.
1. First Rule: Measure Before You Optimize
Before touching a performance hook, ask:
is this component actually slow?
does it re-render often?
is the work expensive?
If you can’t answer yes to at least one, don’t optimize.
React is already fast by default.
2. Understanding Re-Renders (Quick Reality Check)
A component re-renders when:
its state changes
its props change
its parent re-renders
Re-renders are not bugs. They are normal.
Performance issues arise when re-renders do expensive work.
3. useMemo: Cache Expensive Computation
useMemo caches a calculated value, not a render.
Example:
const filteredUsers = React.useMemo(() => {
return users.filter(u => u.active);
}, [users]);Use useMemo when:
computation is expensive
inputs are stable
result is reused
Don’t use it for:
simple mapping
trivial calculations
“just in case” optimization
4. Common useMemo Mistake
Bad:
const value = useMemo(() => ({ a, b }), [a, b]);If you don’t pass this object to memoized children, useMemo is wasted.
Memoization without a consumer is noise.
5. useCallback: Stabilize Function References
useCallback memoizes a function identity.
const onSubmit = React.useCallback(() => {
save(data);
}, [data]);This matters only when:
function is passed to a memoized child
child relies on reference equality
If neither is true, skip it.
6. The Most Common useCallback Anti-Pattern
This is everywhere—and mostly useless:
const handleClick = useCallback(() => {
setOpen(true);
}, []);If:
component is small
handler is not passed down
child isn’t memoized
Then useCallback adds complexity without benefit.
7. Memoization Works in Pairs
Memo hooks only matter when paired with React.memo.
Example:
const ListItem = React.memo(function ListItem({
item,
onSelect,
}: Props) {
return <li onClick={() => onSelect(item.id)}>{item.name}</li>;
});Now useCallback on onSelect actually matters.
Without React.memo, it doesn’t.
8. Avoid Blanket Memoization
This pattern is a smell:
const Component = React.memo(() => {
const handler = useCallback(...)
});This often indicates:
unclear re-render cause
state placed too high
unnecessary abstraction
Fix architecture first.
9. useDeferredValue: Improving Perceived Performance
useDeferredValue lets React delay low-priority updates.
Example: filtering a large list while typing.
const deferredQuery = useDeferredValue(query);
const results = filterItems(items, deferredQuery);Result:
input stays responsive
list updates slightly later
This improves UX without complex logic.
10. When useDeferredValue Is Appropriate
Use it when:
UI must remain responsive
updates are visually expensive
slight delay is acceptable
Common cases:
search-as-you-type
large tables
heavy charts
Do not use it to hide architectural problems.
11. Performance Hooks Do Not Fix Bad State Placement
No hook will fix:
global state overuse
massive contexts
unnecessary provider re-renders
deep prop drilling
If performance feels hard, revisit:
state boundaries
ownership
component responsibilities
Optimization follows architecture.
12. Debugging Performance Correctly
Use:
React DevTools Profiler
component highlight updates
logging render counts
Don’t guess. Measure.
13. A Practical Decision Checklist
Before using a performance hook, confirm:
the component re-renders frequently
the work inside render is expensive
props/state change less frequently
simpler refactors won’t solve it
If all four are true, optimize.
14. Summary
You now know how to:
reason about re-renders calmly
use
useMemofor real computationuse
useCallbackonly when identity matterspair memoization correctly
use
useDeferredValuefor perceived performanceavoid premature optimization traps
Related
Part 7.5 — Build Optimization with Vite: Code Splitting & Env Handling
Vite makes development fast by default. This post shows how to make production builds just as intentional—lean bundles, predictable envs, and code that ships only when needed.
Part 7.3 — Custom Hooks as Architecture: Patterns & Pitfalls
Custom hooks aren’t just helpers. Used well, they define architectural seams in your React app. Used poorly, they hide complexity and make refactoring painful.
Part 7.2 — Advanced Routing Patterns: Protected Routes & URL State
Routing becomes powerful when URLs express intent. This post shows how to protect routes, manage auth flows, and encode UI state in the URL—without hacks.
Comments