React Hooks Deep Dive: useCallback and useMemo
These two hooks confuse a lot of people. Let's break down when you actually need them (and when you don't).
The Premature Optimization Problem
I see codebases wrapped in `useMemo` and `useCallback` everywhere. Most of the time? It's unnecessary and actually makes the code harder to read.
Let's understand what these hooks actually do and when they're worth it.
What They Do
useMemo
Memoizes a computed value:
const expensiveValue = useMemo(() => {
return someExpensiveCalculation(a, b);
}, [a, b]);
The calculation only re-runs when `a` or `b` changes.
useCallback
Memoizes a function reference:
const handleClick = useCallback(() => {
doSomething(a, b);
}, [a, b]);
The function reference stays the same unless `a` or `b` changes.
When You Actually Need Them
useMemo: Expensive Calculations
// Yes - this is actually expensive
const sortedItems = useMemo(() => {
return items.slice().sort((a, b) => a.name.localeCompare(b.name));
}, [items]);
// No - this is not expensive
const fullName = useMemo(() => {
return `${firstName} ${lastName}`;
}, [firstName, lastName]);
String concatenation is not expensive. Sorting 10,000 items might be.
useCallback: Child Component Optimization
// Child uses React.memo
const ExpensiveChild = React.memo(({ onClick }) => {
// expensive render
});
// Parent
function Parent() {
// Without useCallback, onClick is a new reference every render
// This defeats React.memo
const onClick = useCallback(() => {
doSomething();
}, []);
return <ExpensiveChild onClick={onClick} />;
}
useCallback: Effect Dependencies
function Component({ userId }) {
const fetchUser = useCallback(async () => {
const data = await api.getUser(userId);
setUser(data);
}, [userId]);
useEffect(() => {
fetchUser();
}, [fetchUser]); // Stable reference
}
When You Don't Need Them
Inline Event Handlers
// This is fine
<button onClick={() => setCount(count + 1)}>
Increment
</button>
Creating a new function each render is not expensive. React handles this fine.
Simple Transformations
// Just do the calculation
const isEven = count % 2 === 0;
// Don't do this
const isEven = useMemo(() => count % 2 === 0, [count]);
The memoization overhead is probably more expensive than the calculation.
The Rule of Thumb
1. Write the code without memoization first 2. If you have performance issues, profile 3. Add memoization where it actually helps
Don't guess. Measure.
Most React apps are fast enough without obsessive memoization. Focus on writing clear, maintainable code.