useMemo Hook
Learn how the useMemo Hook caches the result of an expensive calculation so it is only recomputed when its dependencies actually change.
Introduction
Every time a React component re-renders, its entire function body runs again from top to bottom. Most of the time this is cheap — creating a few variables or a small JSX tree costs almost nothing. But sometimes a render includes a genuinely expensive calculation: sorting thousands of rows, filtering a huge array, or running a heavy math operation. Redoing that work on every single render, even when the relevant data has not changed, can slow an app down noticeably.
The useMemo Hook lets you tell React "only redo this calculation when these specific values change — otherwise, hand me back the answer you already computed." This lesson walks through exactly what useMemo does, how to use it correctly, and — just as importantly — when to leave it out.
- Why expensive calculations inside a component can hurt performance.
- What useMemo does and how it caches a computed value.
- The syntax for useMemo and its dependency array.
- A worked example: filtering a large list only when needed.
- Why useMemo has its own cost and should not be used by default.
The Problem: Expensive Recalculations
Imagine a component that holds a list of ten thousand products and also has a piece of unrelated state, like whether a sidebar is open. Every time you toggle that sidebar, the component re-renders — and if it filters or sorts the product list directly in the function body, that expensive work runs again even though the product list and the search term never changed.
function ProductList({ products, filterText, sidebarOpen }) {
// This runs on EVERY render, even when only sidebarOpen changes
const filtered = products.filter((p) =>
p.name.toLowerCase().includes(filterText.toLowerCase())
);
return (
<div>
<p>{sidebarOpen ? "Sidebar is open" : "Sidebar is closed"}</p>
<ul>
{filtered.map((p) => (
<li key={p.id}>{p.name}</li>
))}
</ul>
</div>
);
}With ten thousand items, re-running that filter on every unrelated state change can add up to a visibly janky UI. This is exactly the situation useMemo is designed for.
What is useMemo?
useMemo is a React Hook that memoizes — caches — the return value of a function. You give it a calculation function and a dependency array. React runs the function and stores the result on the first render. On every later render, React checks the dependency array: if none of the values have changed since last time, React skips re-running the function and simply returns the cached value from before.
Caches a Value
useMemo remembers the result of a calculation, not the calculation itself.
Dependency-Driven
The cached value is only recalculated when a value in the dependency array changes.
Skips Repeat Work
On renders where dependencies are unchanged, the expensive function does not run at all.
For Expensive Calculations
Intended for genuinely costly work — filtering, sorting, or heavy computation — not everyday values.
useMemo Syntax
useMemo takes two arguments: a function that returns the value you want to cache, and a dependency array. React re-runs the function only when one of the dependencies has changed since the last render.
import { useMemo } from "react";
const memoizedValue = useMemo(() => computeSomething(a, b), [a, b]);- The first argument is a function — useMemo calls it for you, you do not call it yourself.
- The second argument is the dependency array, just like useEffect.
- memoizedValue holds the returned result, not a function.
- An empty array [] means the value is calculated once and reused forever (until the component unmounts).
Example: Filtering a Large List
Let's fix the earlier ProductList component with useMemo. We wrap the filtering logic so it only reruns when products or filterText change — toggling the unrelated sidebarOpen state no longer triggers the expensive filter.
import { useMemo, useState } from "react";
function ProductList({ products, filterText }) {
const [sidebarOpen, setSidebarOpen] = useState(false);
const filtered = useMemo(() => {
console.log("Filtering products...");
return products.filter((p) =>
p.name.toLowerCase().includes(filterText.toLowerCase())
);
}, [products, filterText]);
return (
<div>
<button onClick={() => setSidebarOpen(!sidebarOpen)}>
Toggle Sidebar
</button>
<p>{sidebarOpen ? "Sidebar is open" : "Sidebar is closed"}</p>
<ul>
{filtered.map((p) => (
<li key={p.id}>{p.name}</li>
))}
</ul>
</div>
);
}Filtering products... (only logged when products or filterText changes)
Clicking "Toggle Sidebar" re-renders the component but does NOT log
"Filtering products..." again, because filtered is reused from cache.Notice the dependency array [products, filterText]. As long as those two values stay the same between renders, React returns the previously computed filtered array instead of running the filter function again.
When Not to Use useMemo
It is tempting to wrap every calculation in useMemo "just in case," but useMemo is not free. React still has to store the cached value, compare the dependency array on every render, and manage extra memory — and for a cheap calculation, that bookkeeping can cost more than simply redoing the calculation itself.
- Do not reach for useMemo by default on every calculation — most component logic is cheap enough that it does not matter.
- useMemo has its own overhead: storing a cached value and comparing dependencies on every render.
- Reach for it after you have identified an actual, measurable performance problem — for example with the React DevTools Profiler.
- Simple values like a formatted string or a small sum almost never need useMemo.
Common Mistakes
- Wrapping cheap calculations in useMemo, adding complexity without any real benefit.
- Forgetting a dependency, so the memoized value goes stale and does not update when it should.
- Treating useMemo as a way to avoid re-renders entirely — it only skips recalculating a value, it does not stop the component from rendering.
- Using useMemo for values with side effects — side effects belong in useEffect, not in a memoized calculation.
Best Practices
- Measure first — use the React DevTools Profiler to confirm a calculation is actually slow before memoizing it.
- List every value the calculation depends on in the dependency array, just like useEffect.
- Keep the function passed to useMemo pure — it should not set state or trigger side effects.
- Prefer clear, simple code over premature optimization; readability usually matters more than micro-optimizing.
- Remember useMemo caches a value, while useCallback (covered next) caches a function.
Frequently Asked Questions
Does useMemo prevent my component from re-rendering?
No. The component still re-renders as usual. useMemo only skips recalculating the specific value it wraps if the dependencies have not changed.
What happens if I leave out the dependency array?
Without a dependency array, useMemo recalculates the value on every single render, which defeats the purpose of memoizing it.
Can useMemo make my app slower?
Yes, if used on cheap calculations. Storing the cache and comparing dependencies has its own small cost, so memoizing trivial values can be a net loss.
Is useMemo the same as caching data from an API?
No. useMemo only caches a value within a component's lifetime and re-renders; it is not for caching network requests or persisting data across page reloads.
How do I know when a calculation is "expensive" enough to memoize?
Profile it. If the React DevTools Profiler or a console.time measurement shows the calculation taking a noticeable chunk of render time, especially on large data sets, it is a good candidate.
Key Takeaways
- useMemo caches the result of an expensive calculation between renders.
- It recomputes only when a value in its dependency array changes.
- Syntax: useMemo(() => computeSomething(a, b), [a, b]).
- It is ideal for filtering, sorting, or otherwise costly derived data.
- useMemo has overhead of its own — use it to fix a measured problem, not as a default habit.
Summary
useMemo gives you a precise tool for avoiding repeated, expensive calculations: it caches a computed value and only redoes the work when its dependencies actually change. Used well, it can turn a janky, laggy list into a smooth one — but used everywhere out of habit, it just adds needless complexity.
In this lesson, you saw the performance problem useMemo solves, its syntax, and a worked filtering example, along with a clear warning about when not to reach for it. Next, you will learn about useCallback, which applies the same memoization idea to functions instead of values.
- You understand what useMemo caches and why.
- You can write the useMemo(() => ..., [deps]) syntax correctly.
- You built a worked example that avoids re-filtering a large list unnecessarily.
- You know to reach for useMemo only after finding a real performance problem.