Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding of useMemo (does useMemo actually memoize?)

I am trying to figure out if useMemo has a cache table that maps dependencies with results.

Take a look at this example:

const [a, setA] = useState(0);

const aSquare = useMemo(() => {
  console.log("recalculating aSquare value");
  return a ** 2;
}, [a]);

React documentation of useMemo says:

useMemo is a React Hook that lets you cache the result of a calculation between re-renders.

But the Wikipedia page for memoization says:

Memoization is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls to pure functions and returning the cached result when the same inputs occur again.

If we run the sample and play with the value of a by calling setA with different numbers we'll see that it works just like React documentation says – useMemo "saves" us from unwanted re-calculations (when component re-render is caused by something else like prop changes etc). But re-calculation still happens even if a value repeats itself (like setA(10), then setA(2), then again setA(10)).

Suppose useMemo is doing "classic" memoization, then on the third time instead of recalculating the value, it should be taken from the cache. Does useMemo actually memoize (with caching) or is it only preventing unwanted recalculation like when component re-renders but dependencies did not change?

like image 389
Evok Avatar asked Oct 27 '25 09:10

Evok


2 Answers

Understanding of useMemo (does useMemo actually memoize?)

Yes, useMemo does memoize.

I am trying to figure out if useMemo has a cache table that maps dependencies with results.

No, useMemo does not use a cache table. Here's the source code for useMemo (comments pointing out important parts are from me (// <--)):

function mountMemo<T>( // <-- Runs on first `useMemo` call
  nextCreate: () => T,
  deps: Array<mixed> | void | null,
): T {
  const hook = mountWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  if (shouldDoubleInvokeUserFnsInHooksDEV) {
    nextCreate();
  }
  const nextValue = nextCreate(); // <-- Calculate value
  hook.memoizedState = [nextValue, nextDeps]; // <-- Store value and dependencies
  return nextValue;
}

function updateMemo<T>( // <-- Runs on subsequent `useMemo` call
  nextCreate: () => T,
  deps: Array<mixed> | void | null,
): T {
  const hook = updateWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  const prevState = hook.memoizedState;
  // Assume these are defined. If they're not, areHookInputsEqual will warn.
  if (nextDeps !== null) {
    const prevDeps: Array<mixed> | null = prevState[1];
    if (areHookInputsEqual(nextDeps, prevDeps)) { // <-- Compare dependencies
      return prevState[0]; // <-- If dependencies are equal return previous state
    }
  }
  if (shouldDoubleInvokeUserFnsInHooksDEV) {
    nextCreate();
  }
  const nextValue = nextCreate(); // <-- Otherwise, re-calculate
  hook.memoizedState = [nextValue, nextDeps]; // <-- Store value and dependencies (overwriting the last state)
  return nextValue;
}

– react/packages/react-reconciler/src/ReactFiberHooks.js:L2604-L2638

It stores the calculated value and dependencies in an array which gets passed to future calls of useMemo. In the next call of useMemo, a comparison is made between the dependencies. If they are equal, then the last calculated result is returned. If they are not equal, then the value is re-calculated.

Does useMemo actually memoize (with caching) or is it only preventing unwanted recalculation like when component re-renders but dependencies did not change?

These two aren't mutually exclusive. It is doing both. It's just the cache is very small – it only holds the last result before getting invalidated when the dependencies change.

Suppose useMemo is doing "classic" memoization, then on the third time instead of recalculating the value, it should be taken from the cache.

Drawing from my previous statement and the above quote from your question, I think the idea of a "classic memoization" needs to be examined. There isn't such thing as "classic" memoization. There are only strategies which must be chosen depending on the use-case. This quote from the Wikipedia page nicely suggests this as well:

The set of remembered associations may be a fixed-size set controlled by a replacement algorithm or a fixed set, depending on the nature of the function and its use.

Having a cache has a trade-off – while it saves memory required for computation and time from having to calculate things, it requires memory for storing old values. So with this knowledge, you'll need to decide if useMemo is a suitable tool. If your use-case requires you to have expensive operations based on versions of state older than the previous version, then useMemo may not be for you. I imagine most use-cases won't be like this so useMemo is valuable as a tool in those cases. As with all performance optimisations, start with measuring first. Get familiar with the performance API and your browser's memory or profiler tools.

like image 150
Wing Avatar answered Oct 29 '25 00:10

Wing


reading through their documentation and putting these lines together really looks to indicate that it is only for the most recent value based on the direct dependencies changing:

"...React will call your function during the initial render. On next renders, React will return the same value again if the dependencies have not changed since the last render. Otherwise, it will call calculateValue, return its result, and store it so it can be reused later."

"...React will compare each dependency with its previous value using the Object.is comparison."

"...During next renders, it will either return an already stored value from the last render (if the dependencies haven’t changed), or call calculateValue again, and return the result that calculateValue has returned."

to your point it is like memoization definition from wikipedia but not exact as it isn't as smart as an actual cache like map

also would be a fantastic feature for them to add!!

like image 32
Jacob MacInnis Avatar answered Oct 28 '25 22:10

Jacob MacInnis