DEV Community

loading...

React useMemo and useCallback

alixfachin profile image AlixFachin ・5 min read

Summary

In this article we will look at two react hooks useMemo and useCallback, which will bring us to look at when React renders components.
We will look only at function-based components and not class-based components. (I am not familiar with such class-based components).

Web front-end design draft
Photo by HalGatewood.com on Unsplash

Level required

This post is meant for developers who just learned the basics about React and would like to know a bit more about some hooks.

General idea about useMemo and useCallback

Those two hooks are dealing with code optimization first, and are useful for React component rendering optimization. To understand why those two hooks are important, we need to have a look first at when React renders components.

About component rendering

React is pretty clever in figuring out when to render components and when not to render those.
Basically reacts maintain in memory a virtual DOM tree and checks to see if it needs to re-render this component or not.
For example, React will render a component each time one of its state variable is changed. (For more information, look at the official useState documentation).
What is important to remember is that by default, if a component is rendered, all its children will be rendered again, even if the props values didn't change.
You can see then that if rendering of a child element is expensive (e.g. big computation, network fetch etc...), it becomes interesting to optimize the re-rendering of children components.

How to figure out if a component is rendered

Firefox / Chrome dev tools

Second digression, to see visually if a component has been rendered or not, you can use the React dev tools. In the "component" tab and the "settings" panel (gear wheel) you can check the box "Highlight updates when components render".

Then each time a component renders, its border will flash green.
Choosing React Dev tools rendering monitor in Firefox

Adding a counter

In experimental projects, we can add a counter inside the body of the component. Make sure not to use useState to keep track of this variable value. Changing the state would trigger a re-render, which would increment the counter, which would trigger a re-render, etc. Welcome to the world of infinite loops.
To keep the variable counter into memory, we need to use the useRef hook, which keeps a mutable variable in memory but doesn't trigger a re-render on change.
The code then looks like the following:

function CountComponent(props) {
  const countRef = useRef(0);
  useEffect(() => {
    countRef.current = countRef.current + 1;
  });
  return (<div className="counter">
            <p>Current count: {countRef.current} </p>
          </div>);
}
Enter fullscreen mode Exit fullscreen mode

About useMemo

Basic case

So let's go back to our original matter, the useMemo hook.
The original useMemo documentation explains that useMemo is used to "memoize" an object.
useMemo takes two parameters:

  • an inline function which compute this data's value
  • an array of dependencies which helps React to determine when the value should be recomputed. (if passing an empty array [], the value will be computed only once).

Let's look at the following code:

function App() {
  const exampleData = {myString: "React is great"};
  const memoizedData = useMemo(() => exampleData,[]);
  ...
Enter fullscreen mode Exit fullscreen mode

Each time the App component is rendered, the exampleData will be reloaded in memory, and if it is a big object React will take time to do so.
Using useMemo in the second line will prevent this data to be initialized and as such will save some time.

Combined with the memo() function

Where useMemo really shines is when combined with the React memo() function.

What is the react.memo() function

We mentioned at the top that when React renders a component, all its children component are re-rendered as well, even if their props values didn't change.
Well the React.memo() function is here to help us with that.
The React.memo will almost guarantee that the component is not re-rendered when the props don't change.
Please have a look at the following code:

function CountComponent(props) {
  const countRef = useRef(0);
  useEffect(() => {
    countRef.current = countRef.current + 1;
  });
  return (<div className="counter">
            <p>Current count: {countRef.current} </p>
          </div>);
}

const MemoCountComponent = React.memo( (props) =>  {
  const countRef = useRef(0);
  useEffect(() => {
    countRef.current = countRef.current + 1;
  });
  return (<div className="counter">
            <p>Current count: {countRef.current} </p>
          </div>);
});
Enter fullscreen mode Exit fullscreen mode

We define two components, CountComponent and MemoCountComponent, the latter being encapsulated with a React.memo() function.
If we run this code we can notice that the CountComponent is rendered each time the parent component is rendered, whereas the MemoCountComponent is rendered only once.

Using the useMemo function

As we saw in the previous paragraph, the React.memo() function works very well to prevent un-necessary rendering when props don't change.
However, in order to decide whether props did change or not, React is using a shallow comparison. When the props is a Javascript object, a new reference is created at each render and the React.memo() triggers re-rendering each time.
If we look at the following component:

const MemoPropsCountComponent = React.memo( (props) => {
  const otherCountRef = useRef(0);
  const testString = 'hello';
  useEffect(() => {
    otherCountRef.current++;
  });
  return (<div className="counter">
            <p>Current count: {otherCountRef.current} </p>
            <p> Function:  {props.stringFunction(testString)} </p>
            <p> Data: {JSON.stringify(props.data)} </p>
          </div>);
});
Enter fullscreen mode Exit fullscreen mode

and we include it into an App component,

function App() {
  const exampleData = {test: "React is great"};
  const memoizedData = useMemo(() => exampleData,[]);

  return (
    <div className="App">
      <main>
    <MemoPropsCountComponent data={exampleData} />
    <MemoPropsCountComponent data={memoizedData}/>
     </main>
Enter fullscreen mode Exit fullscreen mode

We can notice that each time React renders the parent component, it renders the first component but doesn't render the second one.
This is a good example of optimizing rendering of children components.

The useCallback hook

React provides a similar hook in order to memoize functions instead of objects.
The official React documentation for useCallback mentions that useCallback(myFunction, dependencyArray) is equivalent to useMemo(()=>myFunction, dependencyArray).
So pretty much everything mentioned above for the useMemo is still valid.

Here is an example of use:

function App() {
  const exampleData = {test: "Oui Monsieur"};
  const memoizedData = useMemo(() => exampleData,[]);

  const stringFunction = (s) => s.split("").reverse().join("");
  const memoizedCB = useCallback(stringFunction, []);

  return (
    <div className="App">
      <main>
        <MemoPropsCountComponent data={exampleData} stringFunction={stringFunction} />
       <MemoPropsCountComponent data={memoizedData} stringFunction={stringFunction} />
       <MemoPropsCountComponent data={memoizedData} stringFunction={memoizedCB} />
      </main>
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

Running the code we can notice that the first two components are rendered at the same time than the parent, but the third component (where all props are memoized) is rendered only once.

Summary

We looked at how useMemo and useCallback can be used very efficiently to prevent re-rendering of children React components, when used in combination with the React.memo() function.
Two points to note:

  • The React API doesn't guarantee that the components will not be re-rendered, but the optimization should be pretty solid.
  • It is recommended not to include any side effects into the useMemo or useCallback hooks. The side effects should be concentrated into the useEffect hook.

Code example

Please have a look at:
https://alixfachin.github.io/react-test-rendering/
to see the code in practice.

Sources - thank you

Should you have any questions or comment, please don't hesitate to contact me:
Github: AlixFachin
LinkedIn: AlixFachin
Twitter: @AlixDev5

Discussion (0)

Forem Open with the Forem app