When and How to Use useMemo in React

When and How to Use useMemo in React

If you’ve built a React app that feels slower than it should, you’ve probably asked yourself two questions: What exactly is being recomputed on every render, and how do I stop it? This is where the useMemo hook in React earns its keep. Used effectively, it prevents expensive recalculations and stabilizes values passed to child components. Used poorly, it adds overhead and confusion.

This React useMemo tutorial by our frontend development company explains when and how to use useMemo, what it actually does, where it is beneficial, where it is not, and how it interacts with React.memo and useCallback. We’ll navigate through realistic useMemo examples in React that will cover the most common use cases software engineers for hire can face in their day-to-day work. Later on, we will present some notes about React 19 useMemo, and a checklist you can keep next to your editor while working on your code.

React useMemo Explained

React useMemo is a hook whose main purpose is to return a memoized value. It takes two arguments: a callback and a dependency array. When the component mounts for the first time, React runs the callback and stores whatever the result is. On each subsequent render, React will return that stored value instead of recomputing the callback function, as long as the dependencies haven’t changed. Should any dependency change, React recomputes the function and stores the new value. This flow repeats again until the component is unmounted.

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

While working with useMemo, there are some points to be aware of. First, it caches values, not functions. As explained above, React will run the provided callback at least once in order to store the returned value. If what you really want to store is a function implementation, you may want to consider useCallback. We will go back to this topic later on.

Second, React useMemo does not “freeze” any variables. The provided dependencies are there just for a reason. If some of them change, the value will recompute. You still may implement useMemo with an empty dependency array (in this case, the value will be only computed once). However, this approach should follow an identified performance bottleneck or the need for stable references, not as a general rule.

Finally, you should not rely on useMemo as a tool to fix logic issues or manage side effects. Moreover, the provided callback should be a pure function; otherwise, you could lead your web application to incur ghostly issues. If you have to take care of side effects, consider relying on the useEffect hook.

Quick Mental model: treat useMemo like a tiny local cache keyed by your dependencies.

When to Reach for useMemo (Rule of Thumb)

A helpful way to decide:

  • Is the computation actually expensive?
  • Does it run frequently (re-renders)?
  • Will caching avoid meaningful work or reduce re-renders downstream (by stabilizing references)?

If you can’t answer “yes” to at least one of these, you might not need it.

Measure first. Before doing optimization, it’s always important to understand what’s going wrong in your app. For that purpose, you can use tools such as React Profiler or your browser’s Performance panel. Once you have a comprehensive knowledge of what’s performing slowly, you can start working on the optimization of such parts. This helps you prevent over-engineering in pieces of your app that really are working well as they are.  

Now, let’s deep dive into some typical scenarios covered by useMemo.

Scenario 1: Expensive Computation You Don’t Want to Repeat

Data manipulation is a frequent technique used in React app. This includes tasks such as sorting, filtering, and aggregation that prepare your data to be displayed according to the designs and UX definitions. However, it’s important to know that having this kind of task in your component implies that every render could recompute a heavy transformation, which in some cases may impact the overall performance of your app.

If you ever face this type of situation, you can use useMemo for improvement. It will store the computed result and use it on each subsequent render, as long as inputs remain the same.

Let’s have a look at the following React useMemo example:

import { useMemo, useState } from "react";
​
function Products({ items }) {
const [query, setQuery] = useState("");
const [sortBy, setSortBy] = useState("price"); // "price" | "rating"
​
const filteredAndSorted = useMemo(() => {
const q = query.toLowerCase().trim();
​
// expensive: filter + sort on a big list
const filtered = items.filter((p) =>
  p.name.toLowerCase().includes(q) ||      p.category.toLowerCase().includes(q)
);
​
const sorted = [...filtered].sort((a, b) =>
  sortBy === "price" ? a.price - b.price : b.rating - a.rating
);
​
return sorted;
}, [items, query, sortBy]);
​
return (
<>
  <Controls query={query} onQueryChange={setQuery} sortBy={sortBy} onSortChange={setSortBy} />
  <List data={filteredAndSorted} />
</>
);
}

Having the sorting function implemented without useMemo implies that every parent re-render triggers a fresh filter+sort of a potentially large list. React performance optimization with useMemo avoids recomputing when items, query, and sortBy stay stable.

Identifying some pieces of code that contain heavy computations and putting them inside useMemo could contribute to improving the performance of your app.  

Scenario 2: Stabilizing Object/Array Props to Prevent Child Re-Renders

Sometimes you might have a heavy component that you want to memorize in order to prevent unnecessary re-renders. You will do that by using React.memo, which will keep a cached version of the component and avoid any new re-render as long as the component’s props don’t change.

Now, let’s say you need to provide this component with a list or an object as a prop. In this case, you end up passing a new object/array literal on every render. Referentially, it’s new, so React.memo sees a prop change and re-renders the child anyway.

To prevent this issue, you can memorize the object/array with useMemo, so the reference is stable when the content hasn’t changed.

import { useMemo } from "react";
import { memo } from "react";
​
const Chart = memo(function Chart({ options }) {
// ... renders only if options reference changes
return <div>{/* chart */}</div>;
});
​
function Dashboard({ theme, currency }) {
const chartOptions = useMemo(() => ({
theme,
axis: { showGrid: true },
currency,
}), [theme, currency]);
​
return <Chart options={chartOptions} />;
}

In the example above, chartOptions keeps the same reference until theme or currency changes, so the Chart component won’t re-render unnecessarily. This is a classic React memoization technique to prevent unnecessary re-renders in React.

Scenario 3: Derived State Instead of Extra useState

A common mistake is to overuse useState to store data that depends on a calculation from another state‘s value. For example: 

function Cart({ items }) {
const [subtotal, setSubtotal] = useState(0);
​
useEffect(()=> {
const result = items.reduce((sum, it) => sum + it.price * it.qty, 0)
​
setSubtotal(result)
}, [items])
​
const tax = subtotal * 0.21;
const total = subtotal + tax;
​
return <Summary subtotal={subtotal} tax={tax} total={total} />;
}

In the previous example, we calculate the subtotal each time the items change, and then we store the result in a new state. The thing is that in this case, you actually don’t need another useState at all—you need a value derived from existing state/props.

Instead of the previous implementation, what you can do is to keep the source of truth singular and compute the derivative with useMemo.

function Cart({ items }) {
const subtotal = useMemo(
() => items.reduce((sum, it) => sum + it.price * it.qty, 0),
[items]
);
const tax = subtotal * 0.21;
const total = subtotal + tax;
​
return <Summary subtotal={subtotal} tax={tax} total={total} />;
}

By doing this, you avoid having a duplicated state, and you won’t have to guess “which value is correct”. Just let useMemo recompute the subtotal when items change.

Scenario 4: Heavy Formatting (i18n, number/date formatting)

We already said that formatting large lists can be surprisingly expensive. Another common technique is to memorize the formatters and then use them in your component. For instance:

function PriceCell({ value, locale, currency }) {
const formatter = useMemo(() => new Intl.NumberFormat(locale, {
style: "currency",
currency
}), [locale, currency]);
​
return <span>{formatter.format(value)}</span>;
}

This way, you create the formatter once and apply it to all the items you want to format.

useMemo vs useCallback (and Where React.memo Fits)

useMemo, useCallback, and React.memo are three ways to take advantage of memoization in order to improve the performance of your application. But they work in different ways. useMemo can be used to memoize a value (including arrays, objects, numbers, strings, and/or JSX trees). useCallback, on the other hand, memoizes a function reference that can be invoked later in your code. Finally, React.memo’s purpose is to memoize a component in order to skip subsequent re-renders if props are shallow-equal.

As the three tools have different objectives, you often combine them, like in the example below:

import { useMemo, useCallback, useState, memo } from "react";
​
const Row = memo(function Row({ item, onSelect, style }) {
// rerenders only if item/onSelect/style identity changes
return <div style={style} onClick={() => onSelect(item.id)}>{item.name}</div>;
});
​
function Table({ items }) {
const [selected, setSelected] = useState(null);
​
const style = useMemo(() => ({ padding: 8, cursor: "pointer" }), []);
const handleSelect = useCallback((id) => setSelected(id), []);
​
return items.map((it) => (
<Row key={it.id} item={it} onSelect={handleSelect} style={style} />
));
}

To sum up, useMemo stabilizes values, useCallback stabilizes functions, and React.memo takes advantage of that stability to skip renders. This trifecta underpins React functional component optimization.

Common Mistakes (and How to Avoid Them)

While useMemo is a powerful tool, it’s important to use it wisely. Here are some common mistakes that you should be aware of:

1) Memoizing cheap work.

Every useMemo has a small cost. Don’t wrap trivial calculations.

2) Wrong dependencies.

If you forget a dependency, you’ll return stale values. If you add too many, you’ll recompute more often than needed. Follow the ESLint rule react-hooks/exhaustive-deps and refactor code so the dependency list is accurate and minimal.

3) Hidden mutations.

useMemo’s result should be immutable. Having a piece of code elsewhere that mutates the store’s value might break the initial assumptions. If your value needs to be changed later on in your code, please consider a different approach than the use of useMemo.

4) Side effects in useMemo.

As stated above, the callback that you provide to useMemo should implement a pure function (i.e., it should always return the same result based on the same arguments). If you need to perform any kind of subscription, logging, or DOM changes, you might take advantage of the specific React hooks available for that purpose (in most cases, this would be handled through useEffect, except for some scenarios in which you may consider the useLayoutEffect hook).

5) Expecting useMemo to “fix” slow rendering trees.

If the rendering of the children’s tree is already slow, you may consider another approach rather than just wrapping them inside a useMemo. Even if this could produce a little improvement in your app, its purpose is other than solely to solve rendering issues. For this case, you might consider splitting components, virtualizing large lists, or memoizing subtrees/components with React.memo. The useMemo hook should complement one of these approaches, instead of being the solution itself.

When to Use useMemo

Below are two practical decision trees that may help you decide whether to use useMemo or not in your component:

Practical decision tree 1 for React useMemo hook usage Practical decision tree 2 for React useMemo hook usage

Measuring the Impact

Let’s say you’ve identified a performance issue within your app. You think about using useMemo in order to prevent some re-computation and save some costs. But right after doing that, you don’t see any improvement. Even worse, you find that the first render of your app is slower than before. 

As you may guess, memoization it’s not free. It demands, at least, some initial computation in order to run the callback and store the initial value. And even if it could improve further re-renders of your component, this may not be the source of the performance issues you’re facing. So, it’s always important to benchmark the performance impact before and after implementing the useMemo hook, in order to decide whether or not its use actually pays off. You can use the following tools for measurement:

  1. React Profiler: record interactions, check “Commit” durations for components with heavy derived values.
  2. Console timings: wrap your heavy computation with console.time/console.timeEnd temporarily.
  3. Flamegraphs in the Performance tab: look for repeated hot functions after idle changes.

If you don’t see any improvement, just remove the memo and simplify. Remember that optimization should be obvious in metrics, not wishful.

React 19 Notes on useMemo

As you may know, React 19 included several features that aim to provide an improvement to React’s performance. Let’s have a quick look at how these features may combine with useMemo. 

At first, the usage of useMemo remains the same as in previous versions: it still returns a memoized value tied to dependencies. But it is worth mentioning that concurrent rendering plays nicer with memoization. In this way, memoized values remain consistent across transitions, and React may prepare UI in the background while using the cached value for the current view.

On the other hand, some of the new Resource-loading patterns, such as Suspense and concurrent data fetching, reduce the need to “precompute everything” in render. This reinforces the idea of keeping useMemo focused on pure, synchronous calculations and reference stability for props.​

Anti-Patterns to Avoid

When working with useMemo in a React custom software solution, you should avoid falling into some anti-patterns that are against React’s philosophy. As we already discussed them across the previous sections, let’s put them together so you can keep this in mind while developing your app: 

  • Wrapping everything in useMemo. This adds noise and overhead, while not necessary and will improve the performance of your app.
  • Memoizing unstable dependencies. If dependencies change each render (e.g., a newly created function not wrapped in useCallback), you’ll recompute every time anyway.
  • Using useMemo to avoid writing clean code. If your computation is slow because it’s doing too much, consider refactoring or precomputing on the server instead of just memoizing values.

Testing and Types

Two main takeaways to keep in mind when working with tests and types:

  • When doing unit tests, don’t try to test useMemo itself; instead, test the computation function.
  • Type the returned value, not the hook. Let inference work:
const total: number = useMemo(() => calcTotal(items), [items]);

Quick Reference: useMemo React Checklist

Follow the checklist below to ensure you’re using useMemo in the right way:

UseMemo React Checklist

FAQ: Short, Practical Answers

Is useMemo guaranteed to memoize?

React may discard memoized values during memory pressure, but in practice, it behaves like a stable cache bound to dependencies. Don’t depend on it across mounts.

Can useMemo replace useState?

No. Use it for derived values, not as a store.

Does useMemo help with async?

No. It’s for synchronous computation during render. Use useEffect/Suspense for async workflows.

Wrap-Up (and What to Do Next)

useMemo isn’t a silver bullet, but it’s a reliable tool when you have expensive calculations or reference identity problems that trigger wasteful re-renders. Measure first, then apply it where it counts. Combine it with React.memo and useCallback for a rounded React hooks for performance strategy—especially on large lists, derived data, and configuration objects passed to memoized children.

If your team is planning a deeper performance pass, consider auditing render paths, splitting components by responsibility, and virtualizing long lists before micro-optimizing everything. Memoization in React components works best as one piece of a broader optimization plan.

Finally, if you’re evaluating a roadmap of improvements or migrating a legacy UI to a modern, memoization-friendly architecture, you may consider hiring custom React JS development services—from profiling and performance tuning to component library design and training in order to ensure a smooth and straightforward transition.