Ever wonder why your React app feels sluggish? Let’s fix those unnecessary re-renders.
When Does React Re-render?
Three triggers:
- Props change
- State changes
- Parent re-renders
The catch? React does shallow comparisons. New object/function references = re-render, even with identical values. That’s where useMemo, useCallback, and React.memo save the day.
The Problem
// ❌ ExpensiveChild re-renders on EVERY keystroke
function Parent() {
const [count, setCount] = useState(0);
const [text, setText] = useState("");
return (
<>
<input value={text} onChange={(e) => setText(e.target.value)} />
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<ExpensiveChild count={count} />
</>
);
}
Solution 1: React.memo
// âś… Only re-renders when count actually changes
const ExpensiveChild = memo(function ExpensiveChild({ count }) {
return <div>Count is: {count}</div>;
});
Solution 2: Component Composition (Even Better!)
Here’s the secret: you often don’t need memo at all.
// âś… BEST: Split components by what changes
function Parent() {
const [count, setCount] = useState(0);
return (
<>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
{/* Pass ExpensiveChild as children */}
<TextInput>
<ExpensiveChild count={count} />
</TextInput>
</>
);
}
function TextInput({ children }) {
const [text, setText] = useState("");
// When text changes, ONLY TextInput re-renders
// children prop stays the same, so React skips that subtree
return (
<>
<input value={text} onChange={(e) => setText(e.target.value)} />
{children}
</>
);
}
function ExpensiveChild({ count }) {
console.log("ExpensiveChild rendered"); // Only logs when count changes!
return <div>Count is: {count}</div>;
}
Why this works:
We split the component in two. The parts that depend on text, together with the text state itself, moved into TextInput. The parts that don’t care about text stayed in Parent and are passed to TextInput as JSX content (the children prop).
When text changes, TextInput re-renders. But it still has the same children prop it got from Parent last time, so React doesn’t visit that subtree. As a result, <ExpensiveChild /> doesn’t re-render.
No memo, no useCallback, no useMemo needed!
Callbacks Are Trickier
// ❌ New function reference every render
const handleClick = () => console.log("Clicked!");
// âś… Stable reference with useCallback
const handleClick = useCallback(() => {
console.log("Clicked!");
}, []);
Pro Tip: Use named functions in
memo()instead of arrow functions. React DevTools will thank you with better debugging instead of “Anonymous” components everywhere.
Quick Reference
Optimization Strategy (in order of preference):
- Component composition (children pattern) - Try this first!
- React.memo - When composition isn’t possible
- useCallback - For callbacks passed to memoized components
- useMemo - For expensive computations
When useCallback and useMemo Become Too Much?
React’s memo hooks are great, until they’re not. Every useCallback and useMemo has its own cost. React must track dependencies, store values, and compare them every render.
The “Over-Optimized” Trap:
const handleChange = useCallback((e) => setFilter(e.target.value), []);
const filtered = useMemo(
() => items.filter((i) => i.match(filter)),
[items, filter]
);
Looks fast? Not really. If items is small and no child is memoized, this “optimization” actually adds overhead and hurts readability.
The Rule of Thumb:
| Situation | Memoization? | Why |
|---|---|---|
| Can restructure with composition | ❌ | Use children pattern first |
| Simple handlers, small lists | ❌ | Overhead > benefit |
| Expensive computations | âś… | Worth caching |
Passing props to React.memo children |
âś… | Prevents re-renders |
| No real performance issue | ❌ | Don’t optimize prematurely |
Pro Tip: Measure first, memoize later. Unnecessary
useMemoanduseCallbackoften make code slower and harder to reason about. And before reaching for memo hooks, ask yourself: “Can I restructure this with component composition?”
Alright, that’s it for this blog post.
More performance tricks coming soon.
Stay tuned and happy coding!