Understanding useMemo and useCallback

Jan 24, 2023by, Aparna Janardhanan

UI

Re-renders often are not a significant concern because React is highly efficient out of the box. But sometimes it does take some time to produce these pictures. Performance issues might result, such as the UI not updating quickly enough following a user activity. 

useMemo and useCallback are essentially tools designed to assist us in refining re-renders. They accomplish this in two ways:

  • Minimizing the amount of work required for a particular render.
  • Minimizing the number of times a component must be re-rendered.

*Image1: medium.com

useMemo

useMemo is a React hook that enables you to cache a calculation’s outcome between re-renders.

const cachedValue = useMemo(calculateValue, dependencies)

For useMemo, you must meet two requirements:

  • A calculation function that takes no arguments, and returns what you wanted to calculate.
  • A list of dependencies that includes each value from each component that is utilized in your computation.

The value you receive from useMemo on the initial render will be the outcome of calling your computation.

React will compare the dependencies with the dependencies you gave during the last render when performing any subsequent renders. useMemo will return the number you previously computed if no dependents have changed since Object.is (as opposed to other methods of checking dependencies). If not, React will recalculate your result and return the updated value.

In other words, unless its dependencies change, useMemo stores a computation result between re-renders. Let’s see how useMemo() works .

Consider a component <CalculateFactorial /> which calculates the factorial of a given number.

import { useState } from "react";

export default function CalculateFactorial() {

   const [number, setNumber] = useState(1);

   const [inc, setInc] = useState(0);

   const factorial = factorialOf(number);

   const onChange = (event) => {

      setNumber(Number(event.target.value));

   };

   const onClick = () => setInc((i) => i + 1);

   return (

     <div>

       Factorial of

       <input type="number" value={number} onChange={onChange} />

       is {factorial}

       <button onClick={onClick}>Re-render</button>

     </div>

 );}

function factorialOf(n) {

    console.log("factorialOf(n) called!");

    return n <= 0 ? 1 : n * factorialOf(n - 1);

}

While changing the input value, the factorial is calculated by calling factorialOf(n) and ‘factorialOf(n) called!’ is logged to the console.

On the other hand, the inc state value is changed each time you click the Re-render button. Updates to the inc state value cause the <CalculateFactorial /> to render again. However, as a side consequence, the factorial is once more calculated during the re-rendering, and “factorialOf(n) called!” is written to the console. Instead of using factorialOf(number), React memoizes the factorial computation by using useMemo(() => factorialOf(number), [number]).

Now let’s improve <CalculateFactorial /> :

import { useState, useMemo } from 'react';

export function CalculateFactorial() {

   const [number, setNumber] = useState(1);

   const [inc, setInc] = useState(0);

   const factorial = useMemo(() => factorialOf(number), [number]);

   const onChange = event => {

      setNumber(Number(event.target.value));

   };

   const onClick = () => setInc(i => i + 1);

   return (

      <div>

        Factorial of

        <input type="number" value={number} onChange={onChange} />

        is {factorial}

        <button onClick={onClick}>Re-render</button>

     </div>

   );

}

function factorialOf(n) {

   console.log('factorialOf(n) called!');

   return n <= 0 ? 1 : n * factorialOf(n - 1);

}

‘FactorialOf(n) called!’ is logged to the console each time the value of the number is changed.

The ‘factorialOf(n) called!’ is not logged to the console when you click the Re-render button because useMemo(() => factorialOf(number), [number]) returns the memoized factorial computation.

useCallback

useCallback hook enables you to cache a function declaration in between re-renders.

const cachedFn = useCallback(fn, dependencies)

For useCallback you must meet two requirements:

  • A function definition you wish to save in a cache between reloads.
  • A list of dependencies containing all values from your component used inside your function, as well as all other values.

The function you specified will be the one returned by useCallback on the initial render.

React will check the dependencies on the subsequent renderings with the dependencies you gave on the prior render. useCallback will return the same function if none of the dependencies have changed since Object.is, as measured by the comparison. Otherwise, useCallback will return the function you passed on this render. In other words, useCallback caches a function until its dependents change in between re-renders.

Consider you have a component which renders a big list of items:

import useSearch from './fetch-items';

function MyBigList({ term, onItemClick }) {

 const items = useSearch(term);

 const map = item => <div onClick={onItemClick}>{item}</div>;

 return <div>{items.map(map)}</div>;

}

export default React.memo(MyBigList);

There may be many items on the list—possibly hundreds. You wrap the list in React.memo() to prevent pointless re-renderings. MyBigList’s parent component offers a handler function that detects when an item is clicked:

import { useCallback } from 'react';

export function MyParent({ term }) {

 const onItemClick = useCallback(event => {

   console.log('You clicked ', event.currentTarget);

 }, [term]);

 return (

   <MyBigList

     term={term}

     onItemClick={onItemClick}

   />

 );

}

onItemClick callback is memoized by useCallback(). UseCallback() returns the same function object if the term is the same. The onItemClick function object does not change when the MyParent component re-renders, maintaining MyBigList’s memoization.

useMemo Vs useCallback

In comparison to useMemo(), useCallback() is a more specialized hook that memoizes callbacks:

import { useCallback } from 'react';

function MyComponent({ prop }) {

 const callback = () => {

   return 'Result';

 };

 const memoizedCallback = useCallback(callback, [prop]);

  return <ChildComponent callback={memoizedCallback} />;

}

As long as the prop dependency is the same as in the above example, useCallback(() =>…, [prop]) returns the same function instance.

The useMemo() function can be used in the same way to memoize callbacks:

import { useMemo } from 'react';

function MyComponent({ prop }) {

 const callback = () => {

   return 'Result';

 };

 const memoizedCallback = useMemo(() => callback, [prop]);

  return <ChildComponent callback={memoizedCallback} />;

}

CONCLUSION

Both useCallback and useMemo demand a function and an array of dependents. When the dependencies change, useCallback returns its function, whereas useMemo calls its function and returns the outcome. useCallback may typically take the role of useMemo, especially when it comes to memory of data and procedures. Maybe React’s upcoming version will do away with the requirement for three different hooks.

Have a project in mind that require complex tech stacks? At Dexlock keep ourselves on track with the latest updates from the tech world. We make it a point to arm ourselves with the upcoming technologies in order to provide the best service to our clients. We believe in creating a futuristic world together with other visionaries like us. Connect with us here for more.

Disclaimer: The opinions expressed in this article are those of the author(s) and do not necessarily reflect the positions of Dexlock.

  • Share Facebook
  • Share Twitter
  • Share Linkedin