React hooks: useMemo

If you are working with React, you have probably heard about the term useMemo. In a nutshell, useMemo is a built-in React hook that allows you to memoize output of a function to prevent it from re-running on every component re-render.

So, we could say, the goal of useMemo hook is to provide a simple way to achieve gains in performance using memoization. But, there is more to it, so let's dive deeper.

How it works

This hook has the following signature.

const memoizedValue = useMemo(compute, dependencies);

You can see that useMemo accepts 2 arguments.

  • compute - function used to compute a result.
  • dependencies - array of dependencies used by compute function.

On the initial render of the component, useMemo will invoke the compute function, memoize the result of the compute function and return the value.

On every component re-render, userMemo will check if dependencies are changed. If change is detected, useMemo will invoke the compute function again, otherwise it will return previously memoized value.

Download more icon variants from https://tabler-icons.io/i/alert-triangle

In programming, memoization is usually implemented in a way that memoization function remembers all inputs and results based on those inputs. Next time, when the same inputs are sent to the memoization function, it will return memoized result.

On the other hand, useMemo only remembers previous inputs and results based on those inputs. As soon as next inputs are not the same as previous inputs, useMemo will run the computation again.

When should it be used

Thing is, useMemo does not give free performance gains. Running useMemo will consume CPU time and memory which can hurt performance if used incorrectly.

For this reason, useMemo should not be used everywhere whenever possible, but rather in some situations where memoization would provide performance benefits.

Overall, make sure that your component does not depend on useMemo at all. Only if you notice performance issues, introduce useMemo. Here are some questions you can ask yourself to decide if useMemo should be used.

  1. Is the component running expensive calculations?
  2. Is the component re-rendering often?
  3. Is the component doing data transformation?

There is no guideline which will tell you when exactly to use this hook. You need to make sure that you understand the problem you are having and then, you will know if useMemo can help you.

Show me some code

It is true that usage of useMemo hook may vary and depend on your component, but it is still possible to provide some examples where useMemo would make sense and where not.

Bad case

As the first example, let's say we want to calculate user age based on the year of birth and the current year. While at it, you might want to add useMemo there, you know, just in case.

const userAge = useMemo(
() => currentYear - yearOfBirth,
[currentYear, yearOfBirth]
);

Well, there you go, this is a bad usage of the useMemo hook. Computers are fast and doing simple calculations like this will take basically no time. Just do the following instead.

const userAge = currentYear - yearOfBirth;
Download more icon variants from https://tabler-icons.io/i/circle-x

When doing calculations on primitive values like numbers, strings, booleans, never wrap them with useMemo. It can take longer to execute useMemo than the calculation itself.

Good case

In this example, there is a function that takes an array of users and formats the activities for every user. Additionally, alongside activities, we want to return users' full name and age.

const activitiesPerUser = useMemo(() => {
return users.map((user) => {
return {
fullName: `${user.firstName} ${user.lastName}`,
age: user.age,
activities: formatUserActivities(user),
};
});
}, [users]);

This function might take a lot of time if formatting of activities is complicated or if the users array is large. In this case, it makes sense to wrap the function in the useMemo hook, especially if the component gets re-rendered multiple times with the same users object.

Download more icon variants from https://tabler-icons.io/i/circle-check

Memoize results of functions that are doing expensive calculations and data transformations. Even if component is not being re-rendered at all, it is still good to wrap those functions with useMemo.

Conclusion

Oh, congrats, you made it to the end. I hope that the useMemo hook is not as complicated anymore.

From my experience, it is always worth memoizing computed non-primitive values, like arrays or objects, that are passed down to the child components. It is even more important if the parent component hosts state variables that are frequently changed. This can prevent unnecessary re-renders of child components.

In general, do not think about performance optimization too much. If your application is not performant, don't worry, you will notice it.

© 2023 Ramo Mujagic. Thanks for visiting.