useMemo, useCallback, React.memo: Memoization Is Not Free

Chris was deeply inspired after taking a React performance optimization course.
"If I memoize every variable and function, it won't have to recalculate anything, so it will definitely be faster, right?"
From that day on, Chris started plastering useMemo and useCallback all over his code.
// ❌ Worst Example: Meaningless Memoization
const one = useMemo(() => 1, []);
const plus = useCallback(() => console.log('plus'), []);What was the result? The app actually became slower, and memory usage increased.
Memoization is not free. It is essentially "spending Memory (RAM) to buy Computation Time (CPU)." If the cost outweighs the benefit, it's better not to do it at all.
Today, we will establish clear criteria for when exactly React's memoization trio is needed and when it should be avoided.
1. useMemo: Remembering Values
useMemo caches the result of a heavy calculation. If the values in the dependency array (deps) haven't changed, it reuses the previously calculated value.
When should you use it?
If "heavy calculation" feels vague, think of it this way:
"Does this code take longer than 0.1ms to run?"
// ✅ Good Example: Filtering and sorting arrays (when data is large)
const sortedUsers = useMemo(() => {
return users.filter(u => u.active).sort((a, b) => b.score - a.score);
}, [users]);
// ❌ Bad Example: Simple transformation
// The cost of setting up useMemo (object creation, comparison) is higher.
const userCount = useMemo(() => users.length, [users]);The React official documentation advises, "There is no benefit to wrapping a calculation unless it involves looping over thousands of items."
2. React.memo: Remembering Components
React.memo is a Higher-Order Component (HOC) that skips re-rendering if the Props haven't changed.
// Child.tsx
const Child = React.memo(({ name, onClick }: { name: string, onClick: () => void }) => {
console.log("Child Rendering!");
return <div onClick={onClick}>{name}</div>;
});However, there is a trap here. When the parent component re-renders, the functions defined inside the parent are also re-created.
To solve this problem, useCallback was introduced.
3. useCallback: Remembering Functions
useCallback allows you to reuse an existing function (reference) instead of creating a new one.
Referential Equality
In JavaScript, {} === {} is false. Objects (including functions) are treated as different if their reference addresses differ, even if their content is the same.
useCallback plays the role of fixing this reference address.
// Parent.tsx
function Parent() {
const [count, setCount] = useState(0);
// ✅ Wrap with useCallback to prevent Child's unnecessary re-render.
const handleClick = useCallback(() => {
console.log("Click");
}, []); // With no dependencies, it maintains the same function reference forever.
return (
<div>
<button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
{/* Now, even if count changes and Parent re-renders, Child stays quiet. */}
<Child name="Chris" onClick={handleClick} />
</div>
);
}Core Rule: useCallback is meaningless if used alone. It is only effective when the child component is wrapped in React.memo. There is no need to use it for functions passed to standard div or button elements.
4. The Cost of Memoization
Why shouldn't we memoize everything?
5. Practical Guidelines
Chris has now set clear criteria.
⭕ When to Use
❌ When NOT to Use
Key Takeaways
Now that we've taken care of performance, it's time to put the app on a diet. Is there any need to download the code for the 'Admin Page' that the user hasn't even entered yet?
Let's learn about Code Splitting, the technique of splitting code into pieces and loading them only when needed.
Continuing in: “Bundle Size Diet: Code Splitting Strategy Using React.lazy and Suspense"