Blog #13: When optimization becomes a burden – Don't 'memo' everything you see
To my past self when I first discovered useMemo and useCallback, thinking that wrapping everything in 'memo' would turn me into a performance master.
Hey there,
I remember the day you discovered useMemo, useCallback, and React.memo. It felt like you'd just been handed a magic wand that could solve all performance problems. You started scattering useMemo everywhere, from simple addition and subtraction to arrays with only a few elements. You confidently told the team: "Don't worry, I've memoized everything, the app will be smooth as silk!".
But did you know that very "dedication" was quietly stifling our application?
1. Simple Explanation: The Notebook Story
Imagine you're a student learning math. The supervisor asks you to calculate 5 + 10. You take out an expensive notebook and carefully write: "If asked for 5 + 10, the result is 15". Then you put the notebook into a safe. Next time the supervisor asks, you have to go to the effort of opening the safe, flipping to the right page to read the result—15.
Doing the mental math of 5 + 10 takes 0.1 seconds, but writing it down and looking it up takes 2 seconds. That is the mistake called Over-memoization.
2. Current Perspective: The hidden cost of optimization
Your old self only saw the benefit of "not having to recalculate." But my current self sees the costs you ignored:
- Memory: Every time you use
useMemo, React must store that value and the dependency array in memory. The more memos, the more RAM used. - Comparison Cost: On every render, React must run a loop to compare each element in the dependency array to see if anything has changed (
===). If your original calculation is faster than running this comparison loop, you're slowing the app down. - Code Complexity: Your code is now cluttered with square brackets and curly braces, making it extremely difficult for others to read and maintain later on.
3. Concrete Example: The "Memo Everything" Trap
You used to write like this and take great pride in it:
// Junior's mistake: Memoizing even breath
const MyComponent = ({ name, age }) => {
// Extremely simple string concatenation still memoized!
const greeting = useMemo(() => `Hello, ${name}`, [name]);
// A simple function not passed anywhere but still useCallback!
const handleClick = useCallback(() => {
console.log('Clicked');
}, []);
return <div onClick={handleClick}>{greeting} - {age}</div>;
};
Why is this a problem? Because useMemo and useCallback in this case are completely meaningless. handleClick isn't passed down to any React.memo component below, so trying to keep its reference brings no benefit to re-renders. You're just wasting memory and CPU for useless comparison tasks.
Sustainable Approach (Pragmatic Approach):
// Only memo when truly necessary
const MyComponent = ({ items, onSelect }) => {
// Only memo for complex calculations (e.g., filter/sort thousands of records)
const sortedItems = useMemo(() => {
return complexSort(items);
}, [items]);
// Only useCallback when passing down to a component wrapped in React.memo
const handleSelect = useCallback((id) => {
onSelect(id);
}, [onSelect]);
return (
<div>
{sortedItems.map(item => (
<HugeExpensiveListItem key={item.id} data={item} onSelect={handleSelect} />
))}
</div>
);
};
4. Comparison: "Doing it Fast" vs "Doing it Sustainably"
| Criteria | Doing it Fast (Wrap all in Memo) | Doing it Sustainably (Profile before Memo) |
|---|---|---|
| Mindset | Memo to be safe, just in case it's faster | Only optimize when a bottleneck is detected |
| Actual Performance | Can be slower due to memo management overhead | Truly optimized where needed |
| Code Cleanliness | Clunky, lots of boilerplate | Clear, easy to understand |
| Technical Debt | Hard to refactor due to tangled dependencies | Easy to change and expand |
Juniors often choose to "wrap everything to be safe." But a true Senior will say: "Don't optimize prematurely."
5. Practical Lesson: Don't guess, measure
I once spent a whole day finding out why an extremely simple Component rendered slowly, even though it was wrapped in memo. Finally discovered the error: I passed an empty object {} as a dependency to useMemo, causing it to always be recalculated, plus the cost of the useMemo itself, making the experience much worse.
The biggest lesson is: Use the Profiler. Chrome browser has a great tool called "React Profiler." Turn it on, see which components are actually re-rendering a lot and taking time, then decide whether to use memo or not.
Don't use feelings to optimize. Use data. And most importantly, remember: simple code is the most powerful code.
Notes for my 24-year-old self, back when I lost the illusion of 'magic wand' performance.
Series • Part 13 of 50