23 May 2019

When is React’s useMemo hook actually worth it?

Jakub Musik

5 min read

table of contents

Share the article

Hooks were introduced into the stable release in the 16.8 React update. One of them is useMemo, designed to help with performance and unnecessary calculations.

When is it actually worth using? The docs say we should use it whenever we make “expensive” calculations. But what is expensive? Will it impact performance when we do it unnecessarily. And by how much? These are the questions I’m going to attempt to answer today.

Since it’s all about performance, if you are not interested in a lengthy explanation, I’ve made a too-long-didn't-read version just for you. However, be aware that it doesn’t contain a lot of interesting insights you can find in the rest of the article. After all, there is only so much time you can save without compromising quality. Let’s cut to the chase:

If you useMemo excessively, performance may suffer by up to 11% (longer mounting and updating).

Most of the time it probably won’t matter.

Use it whenever you feel like and when doing some seriously heavy stuff, such as hundreds of iterations, parsing JSONs or complicated strings operations.

When dealing with referencables and passing them to components optimised with React.memo or PureComponents, it’s worth it as well (useCallback too!).

But let's start by answering a more basic question...

What is useMemo?



You probably know the basic definition of useMemo so I’m not going to bore you with it. What’s important is that useMemo allows you to use memoization to speed up your app. useCallback does the same, but returns a function – basically a special useMemo case, with cleaner syntax. If you want to know more, hop in for a quick one into the React docs.

The question is: when should we useMemo? There are many opinions on this, ranging from never using it, due to possible negative impact on performance, to using it literally everywhere. Others, like me, wonder: where is the golden mean? Let’s find it!

useMemo testing – methodology



I’ve prepared a test project available on my Github. Main points:

In every scenario, the web page is reloaded multiple times using window.location.href to simulate a page opening and measure mounting performance as well.

Performance data for scenarios is stored in sessionStorage (not localStorage—it persists too long). Saving/loading won't affect measurements, as it's done outside the profiler.

To measure performance, I use an experimental React profiler component that supposedly is ready to be merged and made stable.

What I am testing here is just the hook performance. I am ignoring benefits that come from using referenceable objects when passing them to other components that are optimised in regards to rendering (especially with PureComponent or React.memo).

Intermission: Profiler



The Profiler is a new React component that is currently under review and waiting to be merged and released. It allows for simple and effective performance measurements with a non-complicated API. After rendering its children, it executes a callback with performance data. Here, I will make use of:

  • actualDuration – “time spent rendering the Profiler and its descendants for the current (most recent) update”. It will tell me the actual performance during the render. In most applications, it should be quite spikey depending on the actual DOM changes.

  • phase – “mount” or “render” – pretty much self-explanatory. Whether the component mounts or only rerenders.


Why not devtools? Using devtools to measure performance is time consuming when testing multiple scenarios, as it relies on manual review. Also, as a Frontend Developer I need to stay on the top of the bleeding edge, and this research provided an opportunity to do so. With the Profiler component, it is possible to conduct A/B testing, measure performance in real-world conditions, on actual clients, and so much more! For now though, as it is not yet fully released, it’s not available in the production build.

During a quick google search and skimming the docs, no helpful APIs were revealed. If you think I missed something, please let me know.

Is this the “proper” way? I don’t know for sure. I am open to all criticism, even of the non-constructive kind :)

useMemo testing – scenarios



First, let’s discuss the terminology.

Mounting – occurs when a component is rendered for the first time.

Render – every render () call or a functional component call.

Update – when the render result actually changes and the changes are reflected in the DOM. I call it like this because of shouldComponentUpdate

It’s easy to succumb to the temptation to add more and more cases, bloating the data. That’s why I decided to limit myself to:

Low, medium and high workloads

  1. Low – multiply and divide by 2 twice,

  2. Medium – stringify and parse a one-parameter object,

  3. High – stringify and parse a one-parameter object 10 times.

With useMemo or without.

A lot of rendering with sparse updates or a lot of rendering with common updates

  1. Update every sixth render,

  2. Update every second render.



This results in 12 scenarios. Aside from them, there were 2 mounting-only scenarios, with and without the useMemo hook. Everything was run on a MacBook Pro 13" 2.3GHz dual-core i5, with fans cracked to the max to limit thermal throttling, the ever-present performance bottleneck in Apple products.

See also: Code splitting with React & Webpack

useMemo testing – results



If you like raw data, this section is for you.

Keep in mind that any differences should come only from the useMemo hook, as everything else should be the same for scenarios with/without useMemo. What can we learn from the data?

  • Mounting and updating performance was 5-11% lower with the useMemo hook in comparison to no hook.

    mounting and updating performance with and without useMemo chart

    mounting and updating performance with and without useMemo chart

  • Difference between average render times in scenarios with the same workload and useMemo status are very small, which suggests that the Profiler component presents repeatable and trustworthy results.

    average render times with and without useMemo chart

    average render times with and without useMemo chart

  • On average, when mounting and updating are included, useMemo is slower only with very trivial loads.

    average render time in milliseconds with and without useMemo chart

    average render time in milliseconds with and without useMemo chart

  • Only with heavy load is the render (no update, no mount) performance significantly affected – left side of the chart is significantly lower, while the right side is slightly higher with useMemo. When the value changes more often, the average time goes up as well.

    various scenarios with frequent changes performance with and without useMemo chart

    various scenarios with frequent changes performance with and without useMemo chart

    various scenarios with infrequent changes performance with and without useMemo chart

    various scenarios with infrequent changes performance with and without useMemo chart

Raw data

A screenshot of a raw data.

raw data - usememo

useMemo testing – conclusions



The performance cost that comes with using useMemo in React isn’t big, and the potential benefits seem to be quite large. While testing, it was 10,000 useMemos vs 0 useMemos – a situation that’s unlikely to occur in the real world. Initial mount performance suffers a little, but subsequents renders can be sped up by a lot. If your goal is to perform the first render as fast as possible and you are looking to shave even the smallest margins, you should do the rendering on the server side.

Now, that I made this test, I believe that it’s better to useMemo excessively rather than use it sparingly. At the end of the day, it still comes down to two options – use your judgment or measure the impact.

Authors

  • Jakub Musik

    Automatics/robotics engineer turned frontend developer. Involved with React for the majority of his web development career. He loves tweaking his code over and over again to make it even more efficient – sometimes too much for his own good. When he isn’t coding, there is a good chance that he’s playing video games.

The software house. Built to scale with you

free consultation

Good performance is a struggle?

Just tell us what's the problem. We'll be happy to share our experience with you

Book free consultation

Dive deeper into tech