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:
useMemois 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?
Understanding of
useMemo(doesuseMemoactually memoize?)
Yes, useMemo does memoize.
I am trying to figure out if
useMemohas 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
useMemoactually 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
useMemois 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.
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!!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With