| |

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 useEffect or useCallback, 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

ArgumentMeaning
Compute functionRuns when dependencies change
Dependency arrayValues the computation reads
Return valueThe cached or newly computed value

Dependency Array

ArrayBehavior
[]Compute once, cache forever
[a, b]Recompute when a or b changes
MissingRecompute on every render
Object dependencyRecompute when the reference changes

When to Use useMemo

ConditionUse useMemo?
Expensive computation, frequent rendersYes
Cheap computationNo
Referential stability for memoized childYes
Value used as a dependency of another hookYes
Value computed in a handlerNo
Value computed outside the componentNo
Value used onceNo

React.memo and useMemo

PatternPurpose
React.memo(Child)Skip child re-render if props are unchanged
useMemo for object propKeep the object reference stable
useCallback for function propKeep the function reference stable
Without useMemoNew 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

PitfallWhy It HappensFix
Stale valueMissing dependencyAdd the dependency
Recomputes on every renderObject dependency recreatedDepend on the primitive values
No performance benefitComputation is cheapRemove useMemo
Memoized child still re-rendersAnother prop is unstableStabilize all props
Effect runs on every renderObject dependency in useEffectWrap the object in useMemo
Side effect in useMemoConfusing useMemo with useEffectUse useEffect
Complexity without benefituseMemo added by defaultMeasure 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

ItemValue
useMemoCaches a computed value between renders
SignatureuseMemo(compute, deps)
Dependency arrayValues the computation reads
ComparisonObject.is
Empty arrayCompute once, cache forever
Use case 1Expensive computation
Use case 2Referential stability for memoized children
Not forCheap computations, side effects, default habit
Linterexhaustive-deps reports missing/unnecessary deps
MeasurementReact DevTools Profiler

Key takeaways:

  • useMemo caches 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 .
  • useMemo is a performance optimization, not a correctness tool. A component that uses useMemo produces 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. useMemo keeps the reference stable until the dependencies change, which allows React.memo to 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-deps ESLint 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 problem useMemo is meant to solve .
  • Do not use useMemo for 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 in useEffect .
  • 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!