principlejavascriptreactTip
useCallback and useMemo — only optimize when you have a measured problem
Viewed 0 times
React 16.8+
useMemouseCallbackperformancememoizationwhen to optimizeover-optimization
Problem
Developers wrap every function and computed value in useCallback/useMemo by default, believing it always improves performance. In reality, these hooks have overhead (memory for the memoized value, comparison cost on each render) and add cognitive load without benefit in most cases.
Solution
Use useCallback when a function is a dependency of useEffect or passed to a memoized child component. Use useMemo when a computation is genuinely expensive (sort/filter large arrays, complex math). Skip them everywhere else.
// SKIP useMemo for cheap computations
const doubled = value * 2; // just compute it
// SKIP useCallback for inline handlers on non-memoized children
<button onClick={() => setCount(c => c + 1)}>+</button>
// USE useCallback when child is memoized
const Child = React.memo(({ onSave }) => <button onClick={onSave}>Save</button>);
function Parent() {
const handleSave = useCallback(() => {
api.save(data);
}, [data]); // stable reference — Child won't re-render without need
return <Child onSave={handleSave} />;
}
// USE useMemo for expensive computation
const sortedList = useMemo(
() => items.sort((a, b) => a.name.localeCompare(b.name)),
[items]
);
// SKIP useMemo for cheap computations
const doubled = value * 2; // just compute it
// SKIP useCallback for inline handlers on non-memoized children
<button onClick={() => setCount(c => c + 1)}>+</button>
// USE useCallback when child is memoized
const Child = React.memo(({ onSave }) => <button onClick={onSave}>Save</button>);
function Parent() {
const handleSave = useCallback(() => {
api.save(data);
}, [data]); // stable reference — Child won't re-render without need
return <Child onSave={handleSave} />;
}
// USE useMemo for expensive computation
const sortedList = useMemo(
() => items.sort((a, b) => a.name.localeCompare(b.name)),
[items]
);
Why
useMemo and useCallback trade memory and comparison cost for potentially skipping a render or recomputation. For fast operations, the overhead can exceed the savings. Premature optimization also hides intent and makes code harder to read.
Gotchas
- useCallback(fn, deps) returns a new function if deps change — it does NOT prevent re-renders on its own
- Profiling with React DevTools Profiler is the correct way to identify expensive renders
- A dependency-free useCallback(() => fn, []) can still hold a stale closure if fn captures state
- useMemo does not guarantee memoization — React may discard cached values in low-memory situations
Code Snippets
Profile before optimizing
// Profile first
// React DevTools Profiler → record → check what re-renders and why
// Only then add memoization where it matters
const expensiveResult = useMemo(() => heavyCompute(data), [data]);Context
When deciding whether to apply useMemo or useCallback to a function or computed value
Revisions (0)
No revisions yet.