How React useMemo Works: Does It Actually Memoize Values?
React's useMemo hook caches the return value of a computation and only recalculates it when its dependency array changes, using strict equality (Object.is) comparisons on each render.
The useMemo hook is a performance optimization primitive in the React library that memoizes expensive computations to prevent unnecessary recalculations. According to the facebook/react source code, it leverages an internal memo cache slot to store values across renders. Understanding its implementation in packages/react-reconciler/src/ReactFiberHooks.js reveals exactly when and how values are preserved.
How useMemo Works Under the Hood
The implementation spans two critical files in the React codebase.
The Public API in ReactHooks.js
The public-facing hook is defined in packages/react/src/ReactHooks.js. It acts as a thin wrapper that forwards arguments to the current dispatcher:
export function useMemo<T>(create: () => T, deps: Array<mixed> | void | null): T {
return dispatcher.useMemo(create, deps);
}
This entry point (lines 143-148) ensures that useMemo is called during render phases, delegating the actual logic to the reconciler's hook system.
The Reconciler Implementation in ReactFiberHooks.js
The real work occurs in packages/react-reconciler/src/ReactFiberHooks.js. The dispatcher maintains a memo cache slot for each useMemo invocation. On every render, the algorithm performs these steps:
- Reads the previous dependency array (
deps) and cached value from the fiber's hook slot. - Compares each element of the new
depsagainst the old usingObject.isstrict equality. - If any dependency changed, it re-executes the
createcallback, stores the new result, and returns it. - If all dependencies match, it returns the cached value without invoking
create.
This implementation (around lines 1150-1190) ensures that referential equality is maintained across renders when dependencies are stable.
Does useMemo Actually Memoize Values?
Yes—the returned value is cached. When dependencies remain unchanged, useMemo guarantees that the same object reference or primitive is returned on subsequent renders. This behavior is verified in packages/react/src/__tests__/ReactStrictMode-test.js (lines 401-447), where tests confirm identical references across renders.
No—the callback function is not memoized. useMemo does not maintain a stable reference to the create function itself; it only caches the result. If you require a stable function reference, use useCallback, which is internally implemented as useMemo(() => fn, deps).
Strict Mode Double-Invocation Edge Case
In Strict Mode, React deliberately double-invokes the create function during initial mount to help surface side effects. While the memoized value remains stable after both calls, the callback executes twice. This behavior is explicitly tested in ReactStrictMode-test.js and demonstrates an important distinction: the cache stores only the final result, not the process of creation.
Dependency Array Handling
The hook's behavior changes significantly based on how you pass the dependency argument:
undefinedor omitted: Whendepsis omitted,useMemotreats this as "no dependency list" and recreates the value on every render, effectively behaving like a normal function call with no caching.- Empty array
[]: The value is computed once after the first render and never recomputed, mimicking a constant for the component's lifetime. - Populated array
[a, b]: The value recalculates only when specific dependencies change viaObject.iscomparison.
Practical Code Examples
The following examples demonstrate common patterns using the useMemo hook.
Expensive Calculation Caching:
import React, { useMemo, useState } from 'react';
function ExpensiveTree({ size }) {
// buildHugeTree runs only when size changes
const tree = useMemo(() => buildHugeTree(size), [size]);
return <pre>{tree}</pre>;
}
Preserving Referential Equality for Props:
function List({ items }) {
// Maintains stable reference for React.memo children or useEffect dependencies
const filtered = useMemo(() => {
return items.filter(i => i.active);
}, [items]);
return <ItemList data={filtered} />;
}
Observing Strict Mode Behavior:
function Demo() {
const value = useMemo(() => {
console.log('create called');
return Math.random();
}, []); // Logs twice in Strict Mode, but value is identical both times
return <div>{value}</div>;
}
Summary
useMemocaches the result of a function, returning the same reference while its dependency array remains shallow-equal.- The hook uses
Object.iscomparisons to detect changes in the dependency array between renders. - It does not cache the function itself; use
useCallbackfor stable function references. - In Strict Mode, the creation callback may run twice during development, but the memoized value remains stable.
- Passing
undefinedfordepscauses recalculation every render, while[]calculates only once.
Frequently Asked Questions
Does useMemo cache the function or the result?
useMemo caches only the return value of the function, not the function itself. The create callback receives no memoization and is discarded after execution. If you need to memoize a function definition to maintain referential equality (for example, to pass to child components or useEffect dependencies), use the useCallback hook instead.
Why does my useMemo callback run twice in development?
React Strict Mode intentionally double-invokes certain functions during initial mount to help detect side effects. In packages/react/src/__tests__/ReactStrictMode-test.js, tests confirm that while the create callback executes twice, the cached value returned by useMemo remains identical after both invocations. This behavior only occurs in development mode.
What happens if I omit the dependency array in useMemo?
If you omit the deps argument or pass undefined, React treats this as having no dependency list and recalculates the value on every render. This negates any performance benefit. To memoize a value that never changes, pass an empty array [].
When should I use useMemo instead of useCallback?
Use useMemo when you want to cache the result of an expensive computation (objects, arrays, or derived values). Use useCallback when you need a stable function reference to pass to child components or to satisfy dependency arrays in useEffect. Technically, useCallback(fn, deps) is equivalent to useMemo(() => fn, deps).
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s https://instagit.com/install.md Maintain an open-source project? Get it listed too →