Mastering React’s useMemo: When to Cache, Shallow vs Deep Comparisons
This article explains how React's useMemo hook caches expensive calculations, covers its default shallow reference comparison, and shows techniques such as JSON.stringify and lodash.isEqual for deep comparison to prevent unnecessary re‑renders.
Basic Usage
useMemo is a React Hook function used to cache calculation results to avoid unnecessary recomputation. It takes a function and a dependency array and returns a cached value. When any value in the dependency array changes, useMemo recomputes and returns a new cached value.
The basic syntax for useMemo is:
<code>import React, { useMemo } from 'react';
const MyComponent = () => {
const memoizedValue = useMemo(() => {
// perform some calculation or logic
// return the result
}, [dependency1, dependency2]);
return (
<div>
{/* use the cached value */}
<p>{memoizedValue}</p>
</div>
);
};
</code>In the example above, the useMemo hook caches a calculation result or logic. The function passed as the first argument is called once on initial render and only recomputed when dependencies change. The returned value is cached and reused in subsequent renders, avoiding expensive recomputation and improving performance.
React’s useMemo can help reduce component re‑renders by caching results when expensive calculations are involved or when prop changes do not affect the rendered output. The following example shows how to use useMemo to reduce re‑renders:
<code>import React, { useMemo } from 'react';
const MyComponent = ({ data }) => {
// useMemo to cache calculation result
const memoizedValue = useMemo(() => {
// expensive calculation
// return result
}, [data]);
return (
<div>
{/* use the cached value */}
<p>{memoizedValue}</p>
</div>
);
};
</code>In this example,
datais a prop passed to MyComponent. By adding
datato the dependency array, React only recomputes when
datachanges; otherwise it reuses the previous result, avoiding unnecessary re‑renders.
Shallow Comparison
useMemo uses reference equality (shallow comparison) by default. For objects, the reference may change even if the values are the same:
<code>const obj1 = { name: 'John', age: 30 };
const obj2 = { name: 'John', age: 30 };
obj1 === obj2 // false
</code>When you want to avoid recomputation despite a new reference, list each property in the dependency array:
<code>const obj = { name: 'John', age: 30 };
const memoizedValue = useMemo(() => {
// expensive calculation
// return result
}, [obj.name, obj.age]);
</code>For complex objects or arrays, enumerating every property becomes cumbersome. Example with an array:
<code>console.log(fruits); // => ['apple', 'banana', 'grapes']
const yellowFruits = useMemo(
() => fruits.filter(fruit => fruit === "banana"),
[fruits]
);
// => ['banana']
</code>When
fruitschanges, the reference of
yellowFruitschanges, but its contents may stay the same:
<code>console.log(fruits); // => ['apple', 'banana', 'grapes', 'pineapple'] // new content, new reference
const yellowFruits = useMemo(
() => fruits.filter(fruit => fruit === "banana"),
[fruits]
);
// => ['banana'] // same content, new reference
</code>Deep Comparison
When shallow comparison is insufficient, you can deep‑compare dependencies. One approach is to stringify the dependency array:
<code>import React, { useMemo } from 'react';
const MyComponent = ({ data }) => {
const stringifiedData = JSON.stringify(data);
const memoizedValue = useMemo(() => {
// expensive calculation or logic
// return result
}, [stringifiedData]);
return (
<div>
{/* use the cached value */}
<p>{memoizedValue}</p>
</div>
);
};
</code>Be careful: if the stringified version changes even when the actual data hasn't, it can trigger unnecessary recomputation.
Another solution is to use a custom equality check such as
lodash.isEqual:
<code>const _ = require('lodash');
const obj1 = { name: 'John', age: 30 };
const obj2 = { name: 'John', age: 30 };
const obj3 = { name: 'John', age: 25 };
console.log(_.isEqual(obj1, obj2)); // true
console.log(_.isEqual(obj1, obj3)); // false
</code>In a functional component you need
useRefto keep the previous value:
<code>import React, { useMemo, useRef } from 'react';
import _ from 'lodash';
const MyComponent = ({ data }) => {
const ref = React.useRef(data);
const memoizedValue = useMemo(() => {
if (!_.isEqual(ref.current, data)) {
// expensive calculation or logic
ref.current = data;
}
// return result
return ref.current;
}, [data]);
return (
<div>
{/* use the cached value */}
<p>{memoizedValue}</p>
</div>
);
};
</code>This pattern updates the cached result only when
dataactually changes, improving performance.
Some developers propose adding a third argument to useMemo for a custom comparator, but React does not currently support this API.
Summary
useMemo is a useful tool for optimizing React component performance by avoiding unnecessary calculations. It performs shallow comparison of dependencies; for deep comparison you can use JSON.stringify or lodash.isEqual. Similar concepts apply to useEffect and useCallback.
KooFE Frontend Team
Follow the latest frontend updates
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.