skip to Main Content

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

Here are two code samples:


#1:

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

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

#2:

// Custom hook that returns value calculated by processor function whenever deps (dependencies array) changes
// Of course, hook is declared outside of react component
export const useDependentValue = (processor, deps) => {
  const [value, setValue] = useState(processor());

  useEffect(() => {
    const updateValue = () => {
      setValue(processor());
    };

    updateValue();
  }, deps);

  return value;
};
import { useDependentValue } from './hooks'; 


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

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

In both cases I have a two variables:

  • a
  • aSquared, which is "derived"/"dependent" on changes from 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 sample #1 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 were doing "classic" memoization, then on the third time instead of recalculating the value, it should be taken from the cache.

My questions are:

  • Does useMemo actually memoize (with caching) or it’s only preventing unwanted recalculation like when component re-renders but dependencies did not change?
  • Are there any serious differences between code samples #1 and #2?
  • Are there any cases when useDependentValue should be used instead of useMemo for better performance?
  • If dependency array contains some large objects is it better to use useDependentValue instead of useMemo in order to create "dependent" values? Or performance will be the same? (I guess if useMemo actually has some cache table then for large objects useDependentValue should be better).

2

Answers


  1. 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!!

    Login or Signup to reply.
  2. Under the hood, useMemo in React works by storing the dependencies (deps) in a reference each time it is executed. It constantly monitors these dependencies for any changes. If there are changes or if the dependencies are undefined, useMemo will re-run the provided callback function and store its results for future use. However, if the dependencies remain unchanged, useMemo returns the previously memoized value, optimizing performance.

    here is my implementation of useMemo which does the same thing.

    const useMemo = (callback, deps) => {
        const depsRef = useRef(undefined);
        const memoizedValueRef = useRef(undefined);
    
        if (deps !== undefined && !Array.isArray(deps)) {
            throw new Error("deps can only be undefined or an array");
        }
    
        const isNewDepsChangedToArray = depsRef.current === undefined && Array.isArray(deps);
        const isNewDepsUndefiend = !deps;
        const isShallowDifferent = Array.isArray(depsRef.current) && Array.isArray(deps) && (deps.length !== depsRef.current.length || deps.some((dep, i) => dep !== depsRef.current[i]));
    
        if (isNewDepsChangedToArray || isNewDepsUndefiend || isShallowDifferent) {
            memoizedValueRef.current = callback();
        }
    
        depsRef.current = Array.isArray(deps) ? [...deps] : undefined;
    
        return memoizedValueRef.current;
    };
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search