🧠 Stop Using useMemo (Most of the Time)
React gives us two powerful hooks for memoization: useMemo
and useCallback
. But many developers reach for useMemo
prematurely, thinking it's optimizing performance.
Spoiler: It's not. In most cases, you should be reaching for useCallback
— or nothing at all.
🚨 The Problem with useMemo
- It doesn't cache across renders — only within the same render instance.
- It can make performance worse due to unnecessary diffing logic.
- It adds noise to the codebase, making it harder to read and debug.
✅ Why useCallback Is Often the Better Choice
useCallback
is essentially useMemo
for functions — and functions are what usually trigger re-renders in child components.
const handleClick = () => {
console.log("clicked");
};
// Every re-render creates a new function reference
const handleClick = useCallback(() => {
console.log("clicked");
}, []);
// Stable reference between renders
💡 When useMemo
Actually Makes Sense
Use useMemo
only when:
- You're computing an expensive derived value, and
- That value is used in dependencies or passed to memoized children.
const filteredData = useMemo(() => {
return data.filter(item => item.active);
}, [data]);
❌ A Common Mistake
const fullName = useMemo(() => {
return `${user.firstName} ${user.lastName}`;
}, [user.firstName, user.lastName]);
✅ Just do:
const fullName = `${user.firstName} ${user.lastName}`;
🔁 When useCallback Shines
const onToggle = useCallback(() => {
setOpen(prev => !prev);
}, []);
Without useCallback
, this function gets re-created on every render, causing unnecessary re-renders in children that receive it as a prop.
🧼 Final Advice
- Don't use
useMemo
unless profiling shows a real issue. - Use
useCallback
when memoized children depend on functions. - Memoization is a performance tool — not a default coding pattern.
🆕 Enter use()
– The Game Changer
In Server Components, use()
gives us automatic memoization of resolved values — no need to manually optimize with useMemo
or useCallback
.
Because use()
runs once per request on the server, its result is effectively cached across renders — making it memoized by default.
📌 Real-World Example:
async function getUser(id) {
const res = await fetch(`${API_URL}/users/${id}`);
return res.json();
}
export default function UserProfile({ userId }) {
const user = use(getUser(userId));
return (
<div>
<h2>{user.name}</h2>
<p>{user.bio}</p>
</div>
);
}
🧱 Side-by-Side Comparison
Feature | useMemo | useCallback | use() |
---|---|---|---|
Purpose | Memoize values | Memoize functions | Resolve promises |
Scope | Client Components | Client Components | Server Components (RSC) |
Automatic Memo? | No | No | Yes |
Caching | Only within single render | Only within single render | Across renders (per request) |
Best For | Derived values | Event handlers | Async data resolution |
🧠 When Should You Still Use Them?
Even with use()
around, there are still valid use cases for useMemo
and useCallback
:
✅ Keep Using useMemo
When:
- You're deriving complex values (like sorting, filtering large arrays)
- You pass the value to
useEffect
,useContext
, oruseReducer
- You're using it in
React.memo
to preserve referential equality
✅ Keep Using useCallback
When:
- You pass the function to
React.memo
wrapped components - You're using it as a dependency in
useEffect
- You're creating event handlers in dynamic contexts
🧼 Final Thoughts
Rule of thumb:
- Useuse()
in Server Components for async data.
- UseuseCallback
when passing functions to memoized children.
- UseuseMemo
only when profiling shows performance issues.
- Otherwise, keep it simple.
So next time you go to type useMemo
, pause and ask: Is this really needed? Or can I simplify with use()
instead?