React re-renders your components a lot.
Most of the time, that’s totally fine — React is fast, browsers are fast, life is good.
But sometimes… a single line of code inside your component becomes the bottleneck.
It’s not obvious. It’s not broken.
It’s just quietly doing way more work than it needs to, over and over, every time React decides to render.
That’s where useMemo
comes in.
It’s your way of saying:
“Hey React — I already did this work. If nothing important has changed, just give me the same result as last time.”
In this article, we’re going to break down exactly how useMemo
works, when it actually helps, and when it’s just extra baggage in your code.
Table of Contents
Understanding the “Why” Behind useMemo
Building the Mental Model
First Steps with the API
Knowing When (and When Not) to Use It
Solving the “Why Did This Child Re-render?” Mystery
The Optimization Trio
Practical Patterns You’ll Actually Use
Don’t Guess: Measure First
— Pitfalls to Avoid
— Wrap-up & Mental Checklist
Section 1 — Why useMemo
Exists (and When You Don’t Need It)
Picture this:
You’ve built a product page with a huge table — 10,000+ rows of data.
It looks awesome.
But when you change any filter, any sort option, or even update an unrelated bit of state…
…the UI freezes for half a second. 😬
The culprit?
Every render, your component sorts and filters all 10,000 rows from scratch.
Not just when the filter changes — on every render.
Even if you just toggled a “dark mode” switch that has nothing to do with the data, React still recomputes the entire sorted/filtered array.
This is not the same as a “too many keystrokes” problem that you’d fix with a debounce.
Here, the slowdown is caused by heavy calculations running too often — not from events firing too quickly.
Enter useMemo
useMemo
is how you tell React:
“This calculation depends on X and Y.
If X and Y haven’t changed, don’t bother running it again — just give me the last result.”
It’s basically a cache scoped to your component for pure computations.
(Pure means: same inputs → same output, no side effects.)
Without vs. With useMemo
Without:
function ProductTable({ products, sortBy, filter }) {
const processed = processData(products, sortBy, filter); // expensive
return <Table data={processed} />;
}
Even if only the theme changes, processData
runs again.
With:
function ProductTable({ products, sortBy, filter }) {
const processed = React.useMemo(() => {
return processData(products, sortBy, filter);
}, [products, sortBy, filter]);
return <Table data={processed} />;
}
Now it only reprocesses when the actual inputs change — no wasted work.
But… do you even need it?
useMemo
has overhead too. You don’t slap it on every computation.
It’s worth using if:
- The computation is actually expensive (big loops, heavy transformations).
- The component re-renders frequently for reasons unrelated to that computation.
It’s not worth it if:
- The computation is trivial (string concatenation, a quick filter on 5 items).
- The component rarely re-renders.
Think of it like a “do not disturb” sign for calculations — great when needed, pointless otherwise.
✅ Good reason: Heavy data processing or building large objects on every render.
❌ Bad reason: “I saw it in a blog post, so I wrapped everything in it.”
Section 2 — The Mental Model
Think of useMemo
as giving your component its own little vault.
Inside that vault, React stores:
- The last inputs you gave it (dependencies).
- The last result it computed from those inputs.
The deal is simple:
- On the next render, React checks your dependencies.
- If none of them changed, it just hands you the value from the vault. (Boom — instant, no recalculation.)
- If any dependency changed, it re-runs your function, updates the vault, and hands you the new result.
Visualizing it
Let’s say you write:
const sorted = useMemo(() => sortItems(items), [items]);
Render |
items changed? |
What happens? |
---|---|---|
#1 | (first render) |
sortItems runs, vault stores result |
#2 | No | React returns cached result |
#3 | Yes |
sortItems runs again, vault updates |
#4 | No | Cached result returned again |
Why this matters
React re-runs your component function every render — that’s normal.
But useMemo
lets you say:
“This part of my component’s work is expensive.
Only redo it if these specific inputs change.”
It’s like having a smart assistant who remembers:
- When they last did the task.
- What they were given when they did it.
The two golden rules
-
Pure only — The function you give
useMemo
must be pure (no side effects). React might throw away the cached value and re-run it later for any reason. - Dependency accuracy — If the output depends on a variable, that variable must be in the dependencies array. (Otherwise, you risk stale values.)
Section 3 — The API & First Examples
At its core, useMemo
is dead simple.
const memoizedValue = useMemo(
() => {
// some expensive calculation
return result;
},
[dep1, dep2]
);
Let’s break that down:
-
useMemo
— The hook itself. - First argument → A function that returns the value you want to cache.
- Second argument → An array of dependencies. If any of them change between renders, React re-runs the function. If none change, React reuses the cached value.
Example: Sorting products
Without useMemo
:
function ProductList({ products }) {
const sorted = [...products].sort((a, b) => a.name.localeCompare(b.name));
return <List items={sorted} />;
}
Even if products
hasn’t changed, you re-sort on every render.
With useMemo
:
function ProductList({ products }) {
const sorted = useMemo(() => {
console.log("Sorting...");
return [...products].sort((a, b) => a.name.localeCompare(b.name));
}, [products]);
return <List items={sorted} />;
}
Now:
- First render → runs the sort.
- If
products
hasn’t changed → uses cached sorted array.
Try adding a console log — you’ll see the difference.
Example: Heavy math
function Fibonacci({ n }) {
const fib = useMemo(() => {
console.log("Calculating fib...");
return slowFibonacci(n);
}, [n]);
return <div>{fib}</div>;
}
- If
n
stays the same, the expensiveslowFibonacci
never runs again. - Change
n
, and it recalculates.
Dependency Array Rules (This Trips Beginners)
- Include everything your calculation depends on.
- Don’t try to “cheat” by leaving something out — you’ll get stale values.
- ESLint’s
react-hooks/exhaustive-deps
rule helps catch mistakes.
Pure Only — No Side Effects!
This function should:
- Take inputs from dependencies.
- Return a value.
- Not do any data fetching, DOM changes, or state updates.
If you need side effects, that’s useEffect
’s job, not useMemo
’s.
Section 4 — When useMemo
Helps vs. When It’s Useless (or Harmful)
If you hang around dev forums long enough, you’ll hear two extremes:
-
Camp A: “Wrap everything in
useMemo
!” -
Camp B: “Never use
useMemo
!”
The truth?
It’s a tool. Use it when it solves a real problem, skip it when it doesn’t.
✅ When It Helps
1. Expensive Computations
If you’re sorting/filtering big lists, running heavy math, or generating huge derived objects — useMemo
prevents doing that on every render.
Example:
const visibleRows = useMemo(
() => bigList.filter(isVisible),
[bigList, isVisible]
);
2. Referential Stability
React’s re-render system compares references for objects, arrays, and functions.
If you pass a new object to a child each time, it triggers re-renders — even if the content is identical.
useMemo
can keep the same reference as long as the contents haven’t changed:
const tableConfig = useMemo(
() => ({ columns, sortable: true }),
[columns]
);
Great when you use React.memo
on a child and want to avoid unnecessary re-renders.
3. Derived State
Sometimes your component calculates a value from props or other state.
If it’s heavy, memoize it instead of recalculating on every change.
❌ When It’s Useless (or Harmful)
1. Trivial Calculations
const double = value * 2; // ✅ no need for useMemo
React can do this faster than it can check dependencies.
2. Rarely Re-rendered Components
If your component only renders when its props change anyway, caching won’t help much.
3. Premature Optimization
Adding useMemo
everywhere makes your code harder to read and maintain — for zero gain.
Plus, useMemo
itself has overhead. You’re trading one small cost for another.
The Litmus Test
Ask yourself:
- Is this calculation noticeably expensive?
- Does this component re-render often for reasons unrelated to that calculation?
- Will caching the result actually avoid wasted work?
If you can’t answer “yes” to #1 and either #2 or #3 — skip it.
Section 5 — Referential Equality & Child Re-renders
Let’s talk about a subtle but very real problem that happens in React all the time…
You optimize a child component with React.memo
, but it still re-renders every time the parent does. 🤨
The Culprit: New References Every Time
In JavaScript:
- Two primitives (
42
,'hello'
) are equal if they have the same value. - But two objects/arrays/functions are only equal if they’re the same reference in memory.
So:
{} === {} // false
[] === [] // false
That means if you do this in a parent:
<Child config={{ darkMode: true }} />
React sees a brand new object every render — so even if the contents are the same, React.memo
thinks props changed.
How useMemo
Fixes This
You can memoize the object so the reference stays the same unless its contents change:
const config = useMemo(() => ({ darkMode: true }), []);
<Child config={config} />
Now, React.memo
in Child
works as expected — it skips re-rendering when nothing inside config
has changed.
Example: Table with Memoized Config
const Table = React.memo(function Table({ config }) {
console.log("Table rendered");
return <div>{config.title}</div>;
});
function App() {
const title = "My Table";
// Without useMemo: new object each render → Table always re-renders
const config = useMemo(() => ({ title, sortable: true }), [title]);
return <Table config={config} />;
}
- With
useMemo
→ Table only re-renders whentitle
changes. - Without → Table re-renders on every parent render.
Where useCallback
Comes In
-
useMemo
→ memoizes values (objects, arrays, computed results). -
useCallback
→ memoizes functions.
They solve the same problem (stable references), but for different kinds of things.
Example:
const onClick = useCallback(() => console.log("clicked"), []);
<Button onClick={onClick} />
Now Button
won’t re-render just because it got a different onClick
function each time.
TL;DR
- Objects/arrays/functions are compared by reference, not value.
- Without memoization, new references are created on every render.
-
useMemo
(for values) anduseCallback
(for functions) let you keep stable references. - This is one of the most common practical uses of
useMemo
.
Section 6 — useMemo
vs useCallback
vs React.memo
These three tools get confused all the time — partly because their names sound similar, and partly because they all relate to “memoization” (fancy word for “remembering a result so you don’t redo work”).
Let’s clear it up once and for all.
1. useMemo
— Memoize values
- You give it a function that returns something (array, object, computed value).
- It re-runs only when dependencies change.
-
Great for:
- Expensive derived data
- Stable object/array props for memoized children
Example:
const tableConfig = useMemo(() => ({ sortable: true }), []);
2. useCallback
— Memoize functions
- You give it a function, and it gives you back the same function reference until dependencies change.
-
Great for:
- Passing stable callback props to children
- Avoiding re-renders when the child uses
React.memo
Example:
const handleClick = useCallback(() => console.log("clicked"), []);
3. React.memo
— Memoize component renders
- You wrap a component in it.
- React skips re-rendering that component if props are shallowly equal (compared by value for primitives, by reference for objects/functions).
-
Great for:
- Optimizing “pure” components that don’t need to update unless props change
Example:
const Button = React.memo(function Button({ onClick }) {
console.log("Rendered");
return <button onClick={onClick}>Click</button>;
});
How They Work Together
These tools are often used in combos:
const Button = React.memo(function Button({ config, onClick }) {
console.log("Rendered");
return <button onClick={onClick}>{config.label}</button>;
});
function App() {
const config = useMemo(() => ({ label: "Save" }), []);
const onClick = useCallback(() => console.log("Saved!"), []);
return <Button config={config} onClick={onClick} />;
}
Here’s what’s happening:
-
config
object stays stable thanks touseMemo
. -
onClick
function stays stable thanks touseCallback
. -
Button
only re-renders if either prop actually changes, thanks toReact.memo
.
The Quick Decision Table
You want to… | Use… |
---|---|
Memoize a value (array, object) | useMemo |
Memoize a function | useCallback |
Skip re-render if props didn’t change | React.memo |
Pro Tip: Don’t use all three everywhere “just in case.”
Add them when:
- The component is slow to render, or
- The value/function is causing avoidable re-renders.
Section 7 — Practical Patterns You’ll Actually Use
useMemo
sounds fancy in theory, but let’s be honest — until you see it solve real problems, it can feel like abstract “React trivia.”
So here are the common situations where useMemo
isn’t just nice to have… it’s a game-changer.
1. Expensive Derived Data
The scenario:
You’ve got a big dataset — maybe thousands of products, transactions, or users — and you need to sort, filter, or transform it before showing it.
The problem:
Without useMemo
, you’ll re-run that heavy transformation on every render, even when nothing related to it changed. That’s wasted work.
Example:
function ProductTable({ products, filter }) {
const visibleProducts = useMemo(() => {
console.log("Filtering...");
return products
.filter(p => p.category === filter)
.sort((a, b) => a.name.localeCompare(b.name));
}, [products, filter]);
return <List items={visibleProducts} />;
}
- Only recalculates when
products
orfilter
change. - If a parent re-renders for unrelated reasons, the cached value is reused.
When to use:
Filtering large lists, doing heavy math, generating big derived objects.
2. Stable Config Objects for Memoized Children
The scenario:
You pass a config object to a child component that’s wrapped in React.memo
.
The problem:
Without useMemo
, you create a new object each render → child thinks props changed → re-renders anyway.
Example:
const Chart = React.memo(function Chart({ options }) {
console.log("Chart rendered");
return <div>Chart goes here</div>;
});
function Dashboard({ data }) {
const chartOptions = useMemo(() => ({
type: "line",
data,
responsive: true
}), [data]);
return <Chart options={chartOptions} />;
}
-
Chart
only re-renders ifdata
changes, not just because the parent re-rendered. - Saves expensive re-renders, especially for charts/maps/editors.
3. Selector-Style Memoization
The scenario:
You have a huge state object but only care about a small slice of it for this component.
The problem:
If you recalculate that slice on every render, it’s wasteful — especially if it involves non-trivial transformations.
Example:
function Profile({ user }) {
const fullName = useMemo(() => `${user.firstName} ${user.lastName}`, [user]);
return <h2>{fullName}</h2>;
}
Yes, this example is cheap, but imagine generating a complex “profile summary” object from user
data — that’s where useMemo
helps.
4. Memoizing Computed ClassNames or Styles
The scenario:
You’re dynamically generating complex styles or class names based on multiple props.
The problem:
If building that style object is expensive (merging theme objects, computing colors), you don’t want to redo it every render.
Example:
function Card({ isActive, theme }) {
const styles = useMemo(() => {
return {
backgroundColor: isActive ? theme.activeBg : theme.inactiveBg,
padding: "1rem",
borderRadius: "8px"
};
}, [isActive, theme]);
return <div style={styles}>Hello</div>;
}
Stable style object → child components won’t re-render unnecessarily.
5. Parsing or Compiling Artifacts
The scenario:
You’re dealing with data that needs to be parsed, compiled, or transformed in a costly way — e.g., parsing large JSON, compiling markdown to HTML, precomputing regex patterns.
Example:
function MarkdownViewer({ content }) {
const compiled = useMemo(() => markdownToHtml(content), [content]);
return <div dangerouslySetInnerHTML={{ __html: compiled }} />;
}
- Only re-parses the markdown when
content
changes. - Saves you from re-running a potentially slow parser every time.
Pattern Takeaways
- If it’s cheap — skip
useMemo
. - If it’s expensive and you’re re-rendering often — consider it.
- Memoizing values passed to memoized children is one of the most common real-world uses.
Section 8 — Measuring First (Don’t Guess)
One of the easiest ways to waste time as a developer is to “optimize” something that didn’t need optimizing in the first place.
And yes — useMemo
is often guilty of being sprinkled everywhere just because “it sounds fast.”
So let’s make a rule:
Measure before you memoize.
Step 1 — Spotting Symptoms
You might suspect a performance issue if:
- Typing in an input feels laggy.
- Clicking a button causes a noticeable delay.
- Scrolling stutters.
- Animations aren’t smooth.
But suspicion alone isn’t proof — lots of things can cause lag.
We need data.
Step 2 — Use the React DevTools Profiler
React DevTools has a Profiler tab that lets you record a session and see exactly what’s re-rendering and how long it took.
Basic flow:
- Open DevTools → Profiler tab.
- Click the “Record” button.
- Perform the slow action in your app.
- Stop recording.
- Look at the flame chart or ranked list.
Step 3 — What to Look For
If you see:
- Your component re-rendering very often for no good reason
- Expensive computations running on each render
- Child components re-rendering just because a prop reference changed
…then you might have a good case for useMemo
.
If instead you see:
- Your component barely takes time to render
- Most time is spent in the browser painting or layout
- The lag is from network requests or large DOM updates
…then useMemo
won’t help.
Step 4 — Test the Effect of useMemo
If you add useMemo
and re-record:
- Rendering time for that component should drop.
- Child re-renders should disappear (if it was a referential equality issue).
- If nothing changes — remove it. No harm in trying, but don’t keep unnecessary code.
Pro Tip:
If your “expensive computation” runs in under ~1ms, useMemo
probably won’t be worth the extra mental overhead — React can do the work faster than it can check dependencies and return a cached value.
Section 9 — Pitfalls & Mistakes
useMemo
can save you from wasted work — but if you misuse it, you can end up with bugs, stale data, or even slower components.
Let’s look at the most common “gotchas” so you can avoid them.
1. Missing Dependencies (Stale Values)
If your memoized calculation depends on something but you leave it out of the dependency array, React won’t re-run the function when that thing changes — leading to stale results.
const result = useMemo(() => doSomething(data, filter), [data]);
// ❌ forgot `filter` → value never updates when filter changes
Fix: Always include everything your calculation depends on.
const result = useMemo(() => doSomething(data, filter), [data, filter]);
Let ESLint’s react-hooks/exhaustive-deps
rule help you here — it’s basically a safety net.
2. Over-Memoizing
Wrapping every calculation in useMemo
“just in case” can make your code:
- Harder to read
- Slower (because
useMemo
itself has overhead) - More complex to maintain
Rule of thumb: If it’s cheap to compute or the component barely re-renders, skip it.
3. Using useMemo
for Side Effects
The function you give useMemo
must be pure — no data fetching, no state updates, no DOM manipulation.
Bad:
useMemo(() => {
fetchData(); // ❌ side effect
return result;
}, [query]);
If you need to do something (side effect), use useEffect
instead.
4. Confusing useMemo
and useCallback
-
useMemo
memoizes values. -
useCallback
memoizes functions.
If you’re passing a function to a memoized child, use useCallback
, not useMemo
.
5. Ignoring Referential Equality Problems
Sometimes useMemo
is used only to avoid recalculations, but its other superpower is keeping the same reference for objects/arrays.
If you skip this when passing props to React.memo
children, you’ll trigger re-renders unnecessarily.
6. Expecting useMemo
to Prevent All Renders
useMemo
doesn’t stop your component from rendering — it only skips the calculation.
If the parent re-renders, the child still runs — unless you also memoize the child (React.memo
).
Pro Tip:
Treat useMemo
like a scalpel, not a sledgehammer.
It’s precise, effective, and designed for specific jobs — not for wrapping every single line of code “just because.”
Section 10 — Wrap-up & Mental Checklist
We’ve gone from “useMemo
is magic” to “I know exactly when and why to use this thing.”
Now let’s make sure you can spot the right moments in your own code.
The 5-Second useMemo
Checklist
Before adding useMemo
, ask yourself:
Is the calculation actually expensive?
If it’s justx * 2
or filtering 5 items — skip it.Does this component re-render often for unrelated reasons?
If it only re-renders on prop change, caching may not help.Will memoizing avoid wasted work?
Is the same calculation being repeated with the same inputs?Am I passing objects/arrays/functions to a memoized child?
If yes,useMemo
(oruseCallback
) can help keep references stable.Are my dependencies accurate?
No cheating — include everything the calculation depends on.
If you can answer yes to #1 and either #2 or #4, you’re in “good useMemo
territory.”
Key Takeaways
-
useMemo
caches the result of a calculation based on dependencies. - Best for expensive computations and stable prop references.
- Not a magic render-optimizer — it only saves you from redoing work.
- Measure first. Optimize second.
- Keep functions pure and dependencies complete.
Final Word
Think of useMemo
as a polite butler in your component:
It remembers the last task it did, and if you hand it the same instructions again, it just says:
“Here, I already did this for you.”
Use it wisely, and your UI will feel smooth without drowning in unnecessary recalculations.
👉 Coming up next: * The Definitive React 19 useCallback Guide — Patterns, Pitfalls, and Performance Wins*
Follow me on DEV for future posts in this deep-dive series.
https://dev.to/a1guy
If it helped, leave a reaction (heart / bookmark) — it keeps me motivated to create more content
Want video demos? Subscribe on YouTube: @LearnAwesome
Top comments (0)