How to useMemo and useCallback: you can remove most of them

Originally published at https://www.developerway.com. The website has more articles like this 😉

If you’re not completely new to React, you’re probably already at least familiar with useMemo and useCallback hooks. And if you work on a medium to large-scale application, chances are you can describe some parts of your app as an “incomprehensible chain of and s that is impossible to read and debug". Those hooks somehow have the ability to just spread around the code uncontrollably, until they just completely take over and you find yourself writing them just because they are everywhere and everyone around you is writing them.

And do you know the sad part? All of this is completely unnecessary. You can probably remove 90% of all and in your app right now, and the app will be fine and might even become slightly faster. Don't get me wrong, I'm not saying that or are useless. Just that their use is limited to a few very specific and concrete cases. And most of the time we're wrapping things in them unnecessary.

So this is what I want to talk about today: what kind of mistakes developers make with and , what is their actual purpose, and how to use them properly.

There are two major sources of the poisonous spread of those hooks in the app:

  • memoizing props to prevent re-renders
  • memoizing values to avoid expensive calculations on every re-render

We’ll take a look at them later in the article, but first: what exactly is the purpose of and ?

Why do we need useMemo and useCallback

The answer is simple — memoization between re-renders. If a value or a function is wrapped in one of those hooks, react will cache it during the initial render, and return the reference to that saved value during consecutive renders. Without it, non-primitive values like arrays, objects, or functions, will be re-created from scratch on every re-render. memoization is useful when those values are compared. It’s just your normal javascript:

Or, if closer to our typical React use case:

value is a dependency of hook. On every re-render of React will compare it with the previous value. is an object defined within the , which means that on every re-render it will be re-created from scratch. Therefore a comparison of "before re-render" with "after re-render" will return , and will be triggered on every re-render.

To avoid it, we can wrap the value in hook:

Now will be triggered only when the a value actually changes (i.e. never in this implementation).

Exactly the same story with , only it's more useful for memoizing functions:

The most important thing to remember here is that both and are useful only during the re-renders phase. During the initial render, they are not only useless but even harmful: they make React do some additional work. This means that your app will become slightly slower during the initial render. And if your app has hundreds and hundreds of them everywhere, this slowing down can even be measurable.

Memoizing props to prevent re-renders

Now that we know the purpose of those hooks, let’s take a look at their practical usage. And one of the most important ones and the most often used is to memoize props values to prevent re-renders. Make some noise if you’ve seen the code below somewhere in your app:

  1. Had to wrap `onClick` in `useCallback` to prevent re-renders

2. Had to wrap `onClick` in `useCallback` to prevent re-renders

3. Had to wrap `value` in `useMemo`, because it’s a dependency of a memoized `onClick`

Is this something that you’ve done or seen other people around you do? Do you agree with the use case and how the hook solved it? If the answer to those questions is “yes”, congratulations: and took you hostage and unnecessary control your life. In all of the examples, those hooks are useless, unnecessary complicate code, slow down initial render and prevent nothing.

To understand why, we need to remember one important thing about how React works: the reasons why a component can re-render itself.

Why a component can re-render itself?

“Component re-renders itself when state or prop value changes” is common knowledge. Even React docs phrase it like this. And I think this statement is exactly what leads to the false conclusion that “if props don’t change (i.e. memoized), then it will prevent the component from re-render”.

Because there is another very important reason for a component to re-render: when its parent re-renders itself. Or, if we go from the opposite direction: when a component re-renders itself, it also re-renders all of its children. Take a look at this code for example:

component has some state and some children, including component. What will happen when a button is clicked here? State will change, it will trigger App's re-render, and that will trigger re-render of all of its children, including component. It doesn't even have props!

Now, inside of this component, if we have some children as well:

Completely empty, it doesn’t have neither state nor props. But its re-render will be triggered when re-renders, and as a result, it will trigger the re-render of its child. component state change triggers a chain of re-renders across the entire app. See the full example in this codesandbox.

The only way to interrupt this chain is to memoize some of the components in it. We can do it either with hook, or, even better, with React.memo util. Only if the component is wrapped with it will React stop before re-rendering it and check, whether the props value changes.

Memoizing the component:

Using it in App with state change:

In this, and only this scenario it’s important whether props are memoized or not.

To illustrate, let’s assume that component has prop that accepts a function. What will happen if I pass it to without memoizing it first?

will re-render, React will find in its children, and will re-render it. Whether is wrapped in useCallback or not is irrelevant.

And if I memoize ?

will re-render, React will find in its children, realise that it's wrapped in , stop the chain of re-renders, and check first whether props on this component change. In this case, since is a not memoized function, the result of props comparison will fail, and will re-render itself. Finally, some use for :

Now, when React stops on to check its props, will stay the same, and will not be re-rendered.

What will happen if I add another non-memoized value to ? Exactly the same scenario:

React stops on to check its props, will stay the same, but will change, and will re-render itself. See the full example here, try to remove memoization to see how everything starts re-rendering again.

Considering the above, there is only one scenario, when memoizing props on a component makes sense: when every single prop and the component itself are memoized. Everything else is just a waste of memory and unnecessarily complicates your code.

Feel free to remove all and from the code if:

  • they passed as attributes, directly or through a chain of dependencies, to DOM elements
  • they passed as props, directly or through a chain of dependencies, to a component that is not memoized
  • they passed as props, directly or through a chain of dependencies, to a component with at least one prop not memoized

Why remove, not just fix memoization? Well, if you had performance problems because of re-renders in that area, you would’ve noticed and fixed it already, isn’t it? 😉 And since there is no performance problem, there is no need to fix it. Removing useless and will simplify the code and speed up initial render a bit, without negatively impacting existing re-renders performance.

Avoiding expensive calculations on every render

The primary goal of useMemo, according to React docs, is to avoid expensive calculations on every render. No hints though of what constitutes the “expensive” calculation. As a result, developers sometimes wrap in pretty much every calculation in the render function. Create a new date? Filter, map or sort an array? Create an object? for all!

Okay, let’s take a look at some numbers. Imagine we have an array of countries (~250 of them), and we want to render them on the screen and allow users to sort them.

The question is: is sorting an array of 250 elements an expensive operation? Feels like it, isn’t it? We probably should wrap it in to avoid re-calculating it on every re-render, right? Well, easy to measure:

The end result? Without memoization, with 6x CPU slowdown, sorting of this array with ~250 items takes less than 2 milliseconds. To compare, rendering this list — just native buttons with text — takes more than 20 milliseconds. 10 times more! See the codesandbox.

And in real life, the array likely will be much smaller, and whatever is rendered much more complicated, and therefore slower. So the difference in performance will be even bigger than 10 times.

Instead of memoizing the array operation, we should memoize the actual most expensive calculation here — re-rendering and updating components. Something like this:

That drops unnecessary re-renders time of the entire component from ~20ms to less than 2ms.

Considering the above, this is the rule about memoizing “expensive” operations that I want to introduce: unless you actually calculating factorials of big numbers, remove hook on all pure javascript operations. Re-rendering children will always be your bottleneck. Use useMemo only to memoize heavy parts of the render tree.

Why remove though? Wouldn’t it be better to just memoize everything? Wouldn’t it be a compound effect that degrades performance if we just remove them all? One millisecond here, 2 there, and soon our app is not as fast as it could be…

Fair point. And that thinking would be 100% valid, if it wasn’t for one caveat: memoization doesn’t come for free. If we’re using , during the initial render React needs to cache the result value - that takes time. Yes, it will be tiny, in our app above memoizing those sorted countries takes less than a millisecond. But! This will be the true compound effect. The initial render happens when your app first appears on the screen. Every component that is supposed to show up goes through it. In a big app with hundreds of components, even if a third of those memoize something, that could result in 10, 20, at worst maybe even 100 milliseconds added to the initial render.

Re-render, on the other hand, only happens after something in one part of the app changes. And in a well-architectured app only this particular small part going to be re-rendered, not the entire app. How many of the “calculations” similar to the case above we’ll have in that changed part? 2–3? Let’s say 5. Each memoization will save us less than 2 milliseconds, i.e. overall less than 10 milliseconds. 10 milliseconds that may or may not happen (depends on whether the event that triggers it happens), that are not visible with the naked eye, and that will be lost in children’s re-renders that will take 10 times that much anyway. At the cost of slowing down the initial render that will always happen 😔.

Enough for today

That was quite a lot of information to process, hope you found it useful and are now eager to review your apps and get rid of all the useless and that accidentally took over your code. Quick summary to solidify the knowledge before you go:

  • and are hooks that are useful only for consecutive renders (i.e. re-renders), for the initial render they are actually harmful
  • and for props don't prevent re-renders by themselves. Only when every single prop and the component itself are memoized, then re-renders can be prevented. One single mistake and it all falls apart and makes those hooks useless. Remove them if you find them.
  • Remove around "native" javascript operations - compare to components updates those are invisible and just take additional memory and valuable time during the initial render

One small thing: considering how complicated and fragile all of this is, and for performance optimisations really should be your last resort. Try other performance optimisation techniques first. Take a look at those articles that describe some of those:

And of course, goes without saying: measure first!

May this day be your last day in and hell! ✌🏼

Originally published at https://www.developerway.com. The website has more articles like this 😉

Subscribe to the newsletter, connect on LinkedIn or follow on Twitter to get notified as soon as the next article comes out.

--

--

Frontend architect, coder. Love solving problems, fixing things and writing in-depth tech articles: https://www.developerway.com

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Nadia Makarevich

Frontend architect, coder. Love solving problems, fixing things and writing in-depth tech articles: https://www.developerway.com