DEV Community

Joseph Mawa
Joseph Mawa

Posted on

useCallback and useMemo hooks. What are they and how do you use them?

Alt Text

This article is part of the React hooks series. If you haven't read the other articles, you can find them at the links below.

  1. What is useEffect hook and how do you use it?
  2. What is useReducer hook and how do you use it?
  3. What is useState hook and how do you use it?

Content outline

  1. useCallback hook
  2. useMemo hook
  3. References

useCallback hook

A hook is a special function which enables one use state and other react features without writing ES6 class components, according to React hooks documentation.
The useCallback hook is part of the React Hooks API. It takes a callback function and an array of dependencies as arguments. It returns a memoized version of the function passed as argument. If the above definition doesn't make sense, the sections below explain what is useCallback hook, why it is important and how to use it.

In the code below, i have defined two event handlers; eventHandler which we shall later pass as a prop to Child component and clickHandler which is attached as a click event handler on the button element. In React, a component is re-rendered if its state or props change leading to a re-render of its descendants too. For this particular case if we change state of App, its child component Child will also re-render. To prevent Child component from re-rendering if its parent's state changes, i used React.memo. If you are not familiar with React.memo, you can read about it Here. If you now click the button, it will fire the event handler clickHandler which will change state of App. You will see Parent is rendered on the console but not Child is rendered. If we hadn't wrapped Child component in React.memo, changing state of App would make Child component to be re-rendered but this has been prevented by React.memo. This way we can be sure that Child component is only re-rendered due to change in props. You can play with the code Here on Codepen.

import React from "react";
import ReactDOM from "react-dom";

const App = (props) => {
  const [state, setState] = React.useState(0);
  const eventHandler = (e) => {
    console.log(1);
  };
  const clickHandler = (e) => {
    setState((prevState) => prevState + 1);
  };
  console.log("Parent is rendered");
  return (
    <div>
      <button onClick={clickHandler}> click </button>
      <Child />
    </div>
  );
};

const Child = React.memo((props) => {
  console.log("Child is rendered");
  return null;
});

const root = document.getElementById("root");
ReactDOM.render(<App />, root);


Enter fullscreen mode Exit fullscreen mode

You can play with the above code Here on Codepen.

Let us now pass eventHandler as a prop to Child component. Change only one line: <Child />. Pass eventHandler as prop so that we have <Child eventHandler={eventHandler} />.
The above code becomes:

import React from "react";
import ReactDOM from "react-dom";


const App = (props) => {
  const [state, setState] = React.useState(0);
  const eventHandler = (e) => {
    console.log(1);
  };
  const clickHandler = (e) => {
    setState((prevState) => prevState + 1);
  };
  console.log("Parent is rendered");
  return (
    <div>
      <button onClick={clickHandler}> click </button>
      <Child eventHandler={eventHandler} />
    </div>
  );
};

const Child = React.memo((props) => {
  console.log("Child is rendered");
  return null;
});

const root = document.getElementById("root");
ReactDOM.render(<App />, root);

Enter fullscreen mode Exit fullscreen mode

You can play with the above code Here on Codepen.

If you change state of App by clicking the button, Child component will also be re-rendered. Both Parent is rendered and Child is rendered will be console logged. Before passing eventHandler to Child as a prop, a re-render of App doesn't cause a re-render of Child but after passing eventHandler as a prop, Child is re-renderd. This happens because a re-render of App creates a new instance of eventHandler, which has been passed as prop to Child. React will compare instance of eventHandler created for the previous render and for current render. The two won't be equal because a function is an object and two objects tested for equality (strict or non-strict) will only evaluate to true if they have the same reference. For example {} === {}, [] === [] and function(){} === function(){} will all evaluate to false. Strictly speaking, React uses Object.is algorithm to compare eventHandler. This is interpreted by React as change in props resulting in a re-render of Child. This, most likely, is not what you need.

The above behaviour is, in most cases, undesirable. As a result useCallback was developed to fix such problems. It takes a function and array of dependencies as argument. If you pass an empty array, React will always return the same memoized instance of function passed as argument. If an event handler is passed as an argument to useCallback, the same version of the callback is returned in subsequent renders as long as array of dependencies haven't changed. Change eventHandler to:

   const eventHandler = React.useCallback((e) => {
    console.log(1)
  }, [])
Enter fullscreen mode Exit fullscreen mode

If you now change state of App, Child component will not be re-rendered. I hope you notice the empty array passed as second argument. It will ensure the same memoized version of the callback is returned between renders. If you pass an array of dependencies, different memoized version is returned whenever the dependencies change between renders. It should be noted that any value referenced within the callback function (first argument to useCallback) should be passed as a dependency.

useMemo hook

In the above section, we looked at what useCallback hook is and how to use it. It is now time to look at another hook closely related to useCallback, useMemo. The function signature of useMemo is similar to that of useCallback. The difference is that useMemo returns a memoized value while useCallback returns a memoized callback function. Both take a function as first argument and an array of dependencies as second argument.
useMemo hook is used for performing operations which are computationally expensive (Takes a long time to perform or a lot of computer memory). If the dependencies do not change, memoized value will always be returned. If you want the computation to be performed once then pass an empty array.

The code below illustrates how useMemo hook is used.

const App = (props) => {
  const [count, setCount] = React.useState(0);
  const memoizedValue = React.useMemo(() => {
    return Math.random(); //This should be an expensive computation not just generating a random number
  }, []);
  console.log(memoizedValue);
  const clickHandler = (e) => {
    setCount(count + 1);
  };
  return (
    <p>
      <button onClick={clickHandler}> Click </button>
    </p>
  );
};

const root = document.getElementById("root");

ReactDOM.render(<App />, root);

Enter fullscreen mode Exit fullscreen mode

You can play with the above code Here on Codepen.

In the code above, i passed a function which generates and returns a random number(ONLY for illustration). Since i passed an empty array as a dependency, the first random number is memoized and returned on every re-render of the component. If i had not passed a dependency, the random value is computed on every render of the component. The value returned is computed again if the dependencies change

Note

You are strongly advised against using useMemo hook willy-nilly. useMemo hook is used if you are peforming an expensive computation. Do not use it for generating random number like i did in the example. It is meant to illustrate how to use useMemo hook.

Thanks for reading up to the end. If you find anything technically inaccurate, you can comment below and if you find the article enlightening, share it on Twitter or any other social media platform/community. Others might find it useful too.

References

  1. React Hooks API
  2. Stackoverflow

Top comments (0)