React 44 ⚛️ Memoizing Calculated Values with useMemo
A React component re-renders when its state changes, when its props change, or when a parent re-renders. On every re-render, the entire component function runs from top to bottom. Every variable is recreated, every computation is repeated, and every object literal is a new object. Most of the time this is fine. A few arithmetic operations are cheap. A small array map is cheap. The component re-renders, the output is the same or slightly different, and the cost is negligible. But some computations are not cheap. Filtering a list of ten thousand items on every keystroke is expensive. Sorting a large dataset on every render is wasteful when the data has not changed. Creating a new object that is passed to a memoized child component defeats the memoization. useMemo is the hook that caches the result of a computation between renders, so the computation is only repeated when its dependencies change.
The hook is a performance optimization, not a correctness tool. A component that uses useMemo produces the same output as one that does not; the difference is how much work is done to produce it. The React documentation is explicit: useMemo should be used for genuinely expensive computations, not as a default habit. Wrapping every calculation in useMemo adds overhead — the dependency comparison, the cache storage — without a corresponding benefit when the calculation is cheap. The rule of thumb is to measure first, and to apply useMemo only when there is a demonstrable performance problem .
The second common use of useMemo is referential stability. When a component passes an object, an array, or a function to a child that is wrapped in React.memo, the child re-renders if the reference changes. A new object literal on every parent render is a new reference, so the child re-renders even when the contents are identical. useMemo can stabilize the reference by caching the object and returning the same reference until the dependencies change. This is a specific optimization for a specific pattern, and it is often overused. The React documentation notes that useMemo for referential stability is a trade-off: it adds code complexity to avoid re-renders that may not be expensive.
This chapter covers three areas. First, why useMemo exists — the problem of expensive computations on every render and the problem of unstable references that defeat React.memo. Second, how useMemo works — the signature, the dependency array, the caching behavior, and the comparison rules. Third, when to use it and when not to — the criteria for an expensive computation, the referential-stability pattern, and the alternatives that often work better. The chapter ends with a complete example session, a quick reference, best practices, common pitfalls, real-world examples, and diagrams showing the memoization decision.
Key point: useMemo caches the result of a computation between renders and recomputes it only when a dependency changes. It is a performance optimization, not a correctness tool. Use it for genuinely expensive computations and for referential stability when passing objects or functions to memoized children. Do not use it by default; measure first.
Why useMemo exists
The expensive-computation problem. A component function runs on every render. If the function contains a computation that iterates over a large dataset, sorts a large array, or performs a complex transformation, that computation is repeated on every render, even when the inputs have not changed. A search input that filters a list of ten thousand items on every keystroke re-runs the filter on every keystroke, even though the filter logic and the dataset have not changed — only the search term has. If the component also re-renders for unrelated reasons, the filter runs again. useMemo caches the filtered result and recomputes it only when the search term or the dataset changes. The computation is skipped on unrelated renders .
The referential-stability problem. React compares props by reference, not by value. When a parent re-renders, it creates new objects and new functions for its children. Even if the contents are identical, the references are different, so the child re-renders. If the child is wrapped in React.memo, the memoization compares the props by reference, sees a new object, and re-renders. The React.memo optimization is defeated by the new reference. useMemo solves this by caching the object and returning the same reference until the dependencies change. The child receives the same reference, React.memo skips the re-render, and the optimization works. This is the second use of useMemo, and it is the one that is most often overused .
The dependency-array problem. useMemo takes a dependency array. The computation is repeated when any dependency changes, and the cached value is returned otherwise. The comparison is Object.is, which is reference equality for objects and value equality for primitives. A dependency that is an object will trigger a recomputation on every render if the object is recreated, which is exactly the problem useMemo is meant to solve. The dependencies should be the primitive values that the computation actually uses, not the objects that contain them. The React linter enforces this: the exhaustive-deps rule reports missing dependencies and unnecessary ones .
The measurement problem. useMemo has a cost. It allocates a cache, stores the dependencies, and compares them on every render. For a cheap computation, this overhead is larger than the computation itself. The React documentation recommends measuring before applying useMemo. React DevTools Profiler shows which components re-render and how long they take. If a computation is not visible in the profiler, it is not expensive enough to memoize. The rule is: optimize when there is evidence, not when there is speculation .
The alternatives problem. useMemo is not the only tool for performance. Moving a computation out of the component entirely, computing it during the render of a parent that re-renders less often, or using a state variable that is updated only when the inputs change are all alternatives. A useMemo that caches a value which is only used once is unnecessary. A useMemo that caches a value which could be computed in an event handler is misplaced. The hook is for values that are computed during the render and reused across renders. If the value is not reused, the cache is wasted .
The trade-off. useMemo adds code complexity. The dependency array must be maintained, and an incorrect array causes stale values or unnecessary recomputations. The hook makes the component harder to read, because the reader must understand which computations are cached and why. The trade-off is between the performance benefit of avoiding a recomputation and the readability cost of the memoization. For a genuinely expensive computation, the benefit is worth the cost. For a cheap one, it is not.
a. The useMemo signature and behavior
useMemo takes two arguments: a function that computes the value, and a dependency array. It returns the cached value if the dependencies have not changed since the last render, and calls the function and caches the result if they have .
import { useMemo } from 'react';
function ProductList({ products, searchTerm }) {
const filtered = useMemo(() => {
return products.filter((p) =>
p.name.toLowerCase().includes(searchTerm.toLowerCase())
);
}, [products, searchTerm]);
return (
<ul>
{filtered.map((p) => (
<li key={p.id}>{p.name}</li>
))}
</ul>
);
}
The computation function filters the products. It is called on the first render, and the result is cached. On subsequent renders, React compares products and searchTerm with their previous values. If both are the same reference or value, the cached result is returned without calling the function. If either has changed, the function is called again and the new result is cached.
The dependency comparison uses Object.is. For primitives, Object.is is value equality: 1 equals 1, 'a' equals 'a'. For objects, arrays, and functions, Object.is is reference equality: an object equals itself only if it is the same reference. This is why a dependency that is an object recreated on every render causes the computation to run on every render.
The dependency array must include every value from the component’s scope that the computation reads. If the computation reads products and searchTerm, both must be in the array. A missing dependency means the computation uses a stale value; an extra dependency means the computation runs more often than necessary. The exhaustive-deps ESLint rule reports both cases .
An empty dependency array means the computation runs once, on the first render, and the result is cached forever:
const expensiveValue = useMemo(() => computeExpensiveValue(), []);
This is the pattern for a value that never changes. It is equivalent to computing the value once and storing it in a ref, but useMemo is the idiomatic way.
The computation function should be pure. It should not have side effects, because React may call it more than once, or discard the result. The function is called during the render, so it must not mutate state or perform I/O. It should compute a value and return it .
b. The referential-stability pattern
The second use of useMemo is to stabilize a reference. When a parent passes an object or a function to a child that is wrapped in React.memo, the child re-renders if the reference changes. useMemo can keep the reference stable until the dependencies change .
function Parent({ items }) {
const config = useMemo(() => ({ sortBy: 'name', limit: 10 }), []);
return <Child items={items} config={config} />;
}
const Child = React.memo(function Child({ items, config }) {
// re-renders only when items or config changes
return <div>{/* ... */}</div>;
});
Without useMemo, the config object is recreated on every parent render, and Child re-renders every time. With useMemo, the config object is the same reference across renders, and Child skips the re-render when items and config are unchanged.
The same pattern applies to functions:
function Parent({ items, onSelect }) {
const handleSelect = useMemo(
() => (id) => onSelect(id),
[onSelect]
);
return <Child items={items} onSelect={handleSelect} />;
}
The handleSelect function is stable as long as onSelect is stable. If the parent passes handleSelect to a memoized child, the child does not re-render when the parent re-renders for unrelated reasons. The useCallback hook is the specialized version of this pattern for functions; useMemo for functions is equivalent but less idiomatic .
The referential-stability pattern is a trade-off. It adds a dependency array and a memoization wrapper to avoid a re-render that may or may not be expensive. The React documentation notes that the pattern is often used prematurely. If the child’s render is cheap, the re-render is cheap, and the memoization adds complexity without a measurable benefit. The pattern is justified when the child is expensive, when it is wrapped in React.memo, and when the profiler shows that it re-renders unnecessarily.
c. When to use useMemo and when not to
The decision to use useMemo is based on the cost of the computation and the frequency of the re-renders. A computation that is expensive and runs on many renders is a candidate. A computation that is cheap, or that runs on few renders, is not.
Use useMemo when:
- The computation is genuinely expensive. Filtering, sorting, or transforming a large dataset, or a calculation with many steps, qualifies. A few arithmetic operations do not.
- The component re-renders frequently, and the computation runs on every render. A component that re-renders on every keystroke, every mouse move, or every animation frame is a candidate. A component that re-renders once per second is not.
- The result is passed to a memoized child, and the reference must be stable. This is the referential-stability pattern.
- The result is used as a dependency of another hook, such as
useEffectoruseCallback, and the reference must be stable to avoid re-running the effect. This is a related case: an unstable object in a dependency array causes the effect to run on every render.
Do not use useMemo when:
- The computation is cheap. The overhead of the cache and the dependency comparison is larger than the computation itself.
- The component rarely re-renders. If the component renders once per user action, the computation runs once per action, which is not a problem.
- The value is not reused. If the computation result is used once and then discarded, caching it does not help.
- The value could be computed in an event handler. If the computation is triggered by a user action, it can be done in the handler, and the result stored in state. This avoids the memoization entirely.
- The value could be moved out of the component. If the computation does not depend on the component’s state or props, it can be computed at module scope, in a parent component, or in a utility function. This is simpler than memoizing.
The React documentation’s guidance is clear: useMemo is a performance optimization, not a default. The component should be written without it first, and the hook should be added only when the profiler shows a problem. The profiler is the tool that turns speculation into evidence .
Complete Example Session
// ============================================
// PART 1: AN EXPENSIVE COMPUTATION WITHOUT useMemo
// ============================================
function ProductList({ products, searchTerm }) {
// This filter runs on EVERY render
const filtered = products.filter((p) =>
p.name.toLowerCase().includes(searchTerm.toLowerCase())
);
return (
<ul>
{filtered.map((p) => (
<li key={p.id}>{p.name}</li>
))}
</ul>
);
}
// If the component re-renders for an unrelated reason,
// the filter runs again. If products has 10,000 items,
// this is expensive.
// ============================================
// PART 2: THE SAME COMPUTATION WITH useMemo
// ============================================
import { useMemo } from 'react';
function ProductList({ products, searchTerm }) {
const filtered = useMemo(() => {
return products.filter((p) =>
p.name.toLowerCase().includes(searchTerm.toLowerCase())
);
}, [products, searchTerm]);
return (
<ul>
{filtered.map((p) => (
<li key={p.id}>{p.name}</li>
))}
</ul>
);
}
// The filter runs only when products or searchTerm changes.
// Other re-renders reuse the cached result.
// ============================================
// PART 3: THE EMPTY DEPENDENCY ARRAY
// ============================================
function Config() {
const config = useMemo(() => ({
apiUrl: 'https://api.example.com',
timeout: 5000,
}), []);
return <div>{config.apiUrl}</div>;
}
// The object is created once and cached forever.
// It is the same reference on every render.
// ============================================
// PART 4: REFERENTIAL STABILITY FOR A MEMOIZED CHILD
// ============================================
const Child = React.memo(function Child({ config }) {
console.log('Child rendered');
return <div>{config.value}</div>;
});
function Parent({ value }) {
// Without useMemo, a new object on every render
// defeats React.memo on Child.
const config = useMemo(() => ({ value }), [value]);
return <Child config={config} />;
}
// Child re-renders only when value changes.
// ============================================
// PART 5: STABLE CALLBACK WITH useMemo
// ============================================
function Parent({ items, onSelect }) {
const handleSelect = useMemo(
() => (id) => onSelect(id),
[onSelect]
);
return <MemoizedList items={items} onSelect={handleSelect} />;
}
// handleSelect is stable as long as onSelect is stable.
// The memoized list does not re-render unnecessarily.
// ============================================
// PART 6: A DEPENDENCY FOR ANOTHER HOOK
// ============================================
function SearchResults({ query }) {
const options = useMemo(() => ({ query, limit: 10 }), [query]);
useEffect(() => {
fetchResults(options);
}, [options]);
// Without useMemo, options is a new object on every render,
// so the effect runs on every render.
// With useMemo, the effect runs only when query changes.
}
// ============================================
// PART 7: WHEN NOT TO USE useMemo
// ============================================
function SimpleTotal({ items }) {
// This is a cheap computation.
// useMemo would add more overhead than it saves.
const total = items.reduce((sum, item) => sum + item.price, 0);
return <p>Total: {total}</p>;
}
// The reduce is fast, the component re-renders rarely,
// and the memoization is unnecessary.
// ============================================
// PART 8: A COMPUTATION THAT BELONGS IN A HANDLER
// ============================================
function SearchBox({ products }) {
const [results, setResults] = useState([]);
function handleSearch(term) {
// Compute in the handler, store in state
const filtered = products.filter((p) =>
p.name.toLowerCase().includes(term.toLowerCase())
);
setResults(filtered);
}
return (
<>
<input onChange={(e) => handleSearch(e.target.value)} />
<ul>
{results.map((p) => (
<li key={p.id}>{p.name}</li>
))}
</ul>
</>
);
}
// The computation runs on the event, not on every render.
// No useMemo is needed.
// ============================================
// PART 9: A COMPUTATION THAT BELONGS OUTSIDE THE COMPONENT
// ============================================
// Module scope: computed once, not per component
const SORTED_COUNTRIES = COUNTRIES.sort((a, b) =>
a.name.localeCompare(b.name)
);
function CountryList() {
return (
<ul>
{SORTED_COUNTRIES.map((c) => (
<li key={c.code}>{c.name}</li>
))}
</ul>
);
}
// The sort runs once, at module load.
// No useMemo is needed.
// ============================================
// PART 10: THE COMPLETE EXAMPLE
// ============================================
import { useMemo, useState } from 'react';
function ProductSearch({ products }) {
const [searchTerm, setSearchTerm] = useState('');
const [sortOrder, setSortOrder] = useState('asc');
// Expensive computation: filter and sort a large list
const visibleProducts = useMemo(() => {
const filtered = products.filter((p) =>
p.name.toLowerCase().includes(searchTerm.toLowerCase())
);
return filtered.sort((a, b) =>
sortOrder === 'asc'
? a.name.localeCompare(b.name)
: b.name.localeCompare(a.name)
);
}, [products, searchTerm, sortOrder]);
// Stable value passed to a memoized child
const listConfig = useMemo(
() => ({ showPrices: true, sortOrder }),
[sortOrder]
);
return (
<>
<input
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
<select value={sortOrder} onChange={(e) => setSortOrder(e.target.value)}>
<option value="asc">Ascending</option>
<option value="desc">Descending</option>
</select>
<ProductListView products={visibleProducts} config={listConfig} />
</>
);
}
// visibleProducts is recomputed only when products,
// searchTerm, or sortOrder changes.
// listConfig is recomputed only when sortOrder changes.
The ten parts show an expensive computation without useMemo, the same computation with useMemo, an empty dependency array, referential stability for a memoized child, a stable callback, a dependency for another hook, when not to use useMemo, a computation that belongs in a handler, a computation that belongs outside the component, and the complete example.
Quick Reference
useMemo Signature
| Argument | Meaning |
|---|---|
| Compute function | Runs when dependencies change |
| Dependency array | Values the computation reads |
| Return value | The cached or newly computed value |
Dependency Array
| Array | Behavior |
|---|---|
[] | Compute once, cache forever |
[a, b] | Recompute when a or b changes |
| Missing | Recompute on every render |
| Object dependency | Recompute when the reference changes |
When to Use useMemo
| Condition | Use useMemo? |
|---|---|
| Expensive computation, frequent renders | Yes |
| Cheap computation | No |
| Referential stability for memoized child | Yes |
| Value used as a dependency of another hook | Yes |
| Value computed in a handler | No |
| Value computed outside the component | No |
| Value used once | No |
React.memo and useMemo
| Pattern | Purpose |
|---|---|
React.memo(Child) | Skip child re-render if props are unchanged |
useMemo for object prop | Keep the object reference stable |
useCallback for function prop | Keep the function reference stable |
Without useMemo | New reference on every render, defeats React.memo |
Best Practices
✅ Do This:
// Memoize genuinely expensive computations
const filtered = useMemo(() => products.filter(...), [products, term]); // ✅
// Use an empty array for a value that never changes
const config = useMemo(() => ({ ... }), []); // ✅
// Stabilize a reference for a memoized child
const config = useMemo(() => ({ value }), [value]); // ✅
// Stabilize a value used in a dependency array
const options = useMemo(() => ({ query }), [query]); // ✅
// Measure with the profiler before adding useMemo
// React DevTools Profiler // ✅
// Include all dependencies
useMemo(() => compute(a, b), [a, b]); // ✅
❌ Don’t Do This:
// Don't memoize cheap computations
const total = useMemo(() => a + b, [a, b]); // ❌
// Don't memoize a value used once
const x = useMemo(() => compute(), []); return <div>{x}</div>; // ❌
// Don't omit dependencies
useMemo(() => compute(a, b), [a]); // b is stale // ❌
// Don't use useMemo for side effects
useMemo(() => { fetch('/api'); }, []); // use useEffect instead // ❌
// Don't memoize an object recreated from an unstable dependency
useMemo(() => ({ x: obj }), [obj]); // obj must be stable // ❌
// Don't add useMemo by default
// Add it when the profiler shows a problem // ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Stale value | Missing dependency | Add the dependency |
| Recomputes on every render | Object dependency recreated | Depend on the primitive values |
| No performance benefit | Computation is cheap | Remove useMemo |
| Memoized child still re-renders | Another prop is unstable | Stabilize all props |
| Effect runs on every render | Object dependency in useEffect | Wrap the object in useMemo |
Side effect in useMemo | Confusing useMemo with useEffect | Use useEffect |
| Complexity without benefit | useMemo added by default | Measure first |
Real-World Examples
1. Filtered List
const filtered = useMemo(() => products.filter(...), [products, term]);
2. Sorted List
const sorted = useMemo(() => [...items].sort(...), [items]);
3. Empty Dependency Array
const config = useMemo(() => ({ ... }), []);
4. Referential Stability
const config = useMemo(() => ({ value }), [value]);
5. Stable Callback
const handleClick = useMemo(() => () => onClick(id), [onClick, id]);
6. Dependency for useEffect
const options = useMemo(() => ({ query }), [query]);
useEffect(() => { fetchResults(options); }, [options]);
7. Expensive Calculation
const stats = useMemo(() => computeStats(data), [data]);
8. Grouped Data
const grouped = useMemo(() => groupBy(items, 'category'), [items]);
9. Derived Value
const total = useMemo(() => items.reduce((s, i) => s + i.price, 0), [items]);
10. Complete Search and Sort
const visible = useMemo(() => {
const filtered = products.filter(...);
return filtered.sort(...);
}, [products, searchTerm, sortOrder]);
Visual
The useMemo Cache
┌──────────────────────────────────────────────────────────────┐
│ THE useMemo CACHE │
│ │
│ Render 1 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ useMemo(() => compute(a, b), [a, b]) │ │
│ │ │ │
│ │ Dependencies: [a, b] │ │
│ │ No previous cache → run compute() │ │
│ │ Result: R1 │ │
│ │ Cache: { deps: [a, b], value: R1 } │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ Render 2 (a changed) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Dependencies: [a', b] │ │
│ │ a' !== a → run compute() │ │
│ │ Result: R2 │ │
│ │ Cache: { deps: [a', b], value: R2 } │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ Render 3 (nothing changed) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Dependencies: [a', b] │ │
│ │ Same as cache → return cached value │ │
│ │ No compute() call │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
The Referential Stability Problem
┌──────────────────────────────────────────────────────────────┐
│ REFERENTIAL STABILITY │
│ │
│ WITHOUT useMemo: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ function Parent() { │ │
│ │ const config = { value: 1 }; ← new object │ │
│ │ return <MemoizedChild config={config} />; │ │
│ │ } │ │
│ │ │ │
│ │ Parent renders → new config object │ │
│ │ MemoizedChild compares props by reference │ │
│ │ config !== previous config → re-render │ │
│ │ React.memo is DEFEATED │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ WITH useMemo: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ function Parent() { │ │
│ │ const config = useMemo(() => ({ value: 1 }), []); │ │
│ │ return <MemoizedChild config={config} />; │ │
│ │ } │ │
│ │ │ │
│ │ Parent renders → same config reference │ │
│ │ MemoizedChild compares props by reference │ │
│ │ config === previous config → skip re-render │ │
│ │ React.memo WORKS │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
The Decision Flow
┌──────────────────────────────────────────────────────────────┐
│ useMemo DECISION FLOW │
│ │
│ Is the computation expensive? │
│ │ │
│ ├── NO ──► Do NOT use useMemo │
│ │ │
│ └── YES ──► Does the component re-render frequently? │
│ │ │
│ ├── NO ──► Do NOT use useMemo │
│ │ │
│ └── YES ──► Is the result passed to a │
│ memoized child or used as a │
│ dependency? │
│ │ │
│ ├── NO ──► Maybe use useMemo │
│ │ (measure first) │
│ │ │
│ └── YES ──► USE useMemo │
│ │
│ Always measure with React DevTools Profiler before │
│ adding useMemo. │
│ │
└──────────────────────────────────────────────────────────────┘
The Dependency Array
┌──────────────────────────────────────────────────────────────┐
│ THE DEPENDENCY ARRAY │
│ │
│ COMPUTATION READS: products, searchTerm │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ useMemo(() => { │ │
│ │ return products.filter(p => │ │
│ │ p.name.includes(searchTerm) │ │
│ │ ); │ │
│ │ }, [products, searchTerm]) ← both dependencies │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ WRONG: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ }, []) ← missing deps │ │
│ │ }, [searchTerm]) ← products is stale │ │
│ │ }, [products, searchTerm, x]) ← x is unnecessary │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ The exhaustive-deps ESLint rule reports both missing │
│ and unnecessary dependencies. │
│ │
│ The comparison is Object.is: │
│ Primitives: value equality │
│ Objects: reference equality │
│ │
└──────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
useMemo | Caches a computed value between renders |
| Signature | useMemo(compute, deps) |
| Dependency array | Values the computation reads |
| Comparison | Object.is |
| Empty array | Compute once, cache forever |
| Use case 1 | Expensive computation |
| Use case 2 | Referential stability for memoized children |
| Not for | Cheap computations, side effects, default habit |
| Linter | exhaustive-deps reports missing/unnecessary deps |
| Measurement | React DevTools Profiler |
Key takeaways:
useMemocaches a computed value. The computation function runs on the first render and is cached. On subsequent renders, React compares the dependencies and returns the cached value if they have not changed. The computation is skipped on unrelated renders .useMemois a performance optimization, not a correctness tool. A component that usesuseMemoproduces the same output as one that does not. The hook only affects how much work is done to produce the output. It should not change the behavior of the component .- Use it for genuinely expensive computations. Filtering, sorting, or transforming a large dataset is a candidate. A few arithmetic operations are not. The overhead of the cache and the dependency comparison is larger than the computation itself for cheap values .
- Use it for referential stability. When an object or function is passed to a memoized child, or used as a dependency of another hook, the reference must be stable.
useMemokeeps the reference stable until the dependencies change, which allowsReact.memoto skip re-renders and prevents effects from running on every render . - The dependency array must include every value the computation reads. A missing dependency causes a stale value. An extra dependency causes unnecessary recomputations. The
exhaustive-depsESLint rule reports both cases . - The comparison is
Object.is. For primitives, this is value equality. For objects, it is reference equality. A dependency that is an object recreated on every render causes the computation to run on every render, which is the problemuseMemois meant to solve . - Do not use
useMemofor side effects. The computation function should be pure. It runs during the render, and React may call it more than once or discard the result. Side effects belong inuseEffect. - Measure before optimizing. React DevTools Profiler shows which components re-render and how long they take. If a computation is not visible in the profiler, it is not expensive enough to memoize. The hook should be added in response to evidence, not speculation .
Remember: useMemo is a performance optimization for values that are computed during the render and reused across renders. It caches the result and recomputes it only when the dependencies change. It is not a correctness tool, and it should not be used by default. The two legitimate uses are genuinely expensive computations and referential stability for memoized children or hook dependencies. The dependency array must include every value the computation reads, and the comparison is reference equality for objects. For everything else — cheap computations, values used once, values computed in handlers, values computed outside the component — useMemo adds complexity without benefit. Measure first, then memoize.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!