Photo by Christophe Hautier on Unsplash
The useMemo/useCallback addiction
June 24, 2022 • 5 min readAs developers, we should know that at some point in the software development process we'll face one really common concern: performance. Sometimes it will show up in the early phases, sometimes a lot later down the road but if we are not knowingly overlooking them, they'll show up. This is why many tools and concepts try to focus on how to provide us with resources that we can use while freeing us from the many inners of how those resources work. I don't see that as a bad thing at all, I like how easy it is nowadays to play with performance improvements in our apps. But most of the time the problem lies in when or how we receive and adopt those mechanisms.
We all heard about React hooks when they were announced in Dan Abramov's talk for React Conf 2018 and later when they were released back in February 2019. There was a huge buzz around React v16.8 mostly because of hooks. In this post, I wanted to focus on two of the hooks released with that version since those are hooks that relate to the topic we started this blog post with. They are useMemo
and useCallback
.
Why all the buzz around useMemo
and useCallback
?
Likewise most of the new stuff we constantly get from these famous tools (especially in the front-end development ecosystem), hooks were quickly adopted, including in the project I was working on at that time. To me, one of the reasons that helped this fast adoption is the fact that internally hooks provide a more efficient way of handling state without having to rely on a class component and its lifecycle events. Another good attractive is how "easy" it is to apply performance optimizations to a component and avoid re-renders of your components. But a few years later I've come to wonder if the latter reason ended up being more of a pitfall than a good selling point.
I've been through a few different projects since React hooks were released and, being part of all the buzz created, I noticed how often we would sell useMemo
and useCallback
as important mechanisms to use when thinking about performance. I've seen more often than not people using those hooks carelessly almost as if they're following a rule of every function defined in a component's body needs to be proxied through useCallback
and every value needs to be memoized! 💪
I understand why we'd easily go for that. Supposedly, they are hooks that, without much cognitive effort, give us the ability to decrease re-renders of our components and increase performance. But is that really how things work?
How do those hooks differ from each other?
These two hooks work in slightly different ways to achieve a common goal: reusing previously calculated values if the variables used in the calculation are the same, aka memoization.
While useMemo will give you a cached value if the dependencies passed have not changed, useCallback will not reassign you a new callback if the dependencies have not changed. By the looks of it, we might think that these are good things to avoid all the time in our components, right? But the question is: if that is the case then why isn't React giving us that by default?? 🤔
Why it does NOT make sense to always memoize stuff?
It is important to remember that with pros we always get cons and we should always weigh them before adding them to our basket. React does not provide us that behavior by default whenever defining a function in our component's body because sometimes there simply isn't an issue with a function being redefined every time a component re-renders. Outside of extreme scenarios, redefining a function at every re-render should not be a concern, especially when there are no indications that that is the cause for your app being slow.
The same thing is valid when it comes to avoiding recalculating a value, which is when we would apply useMemo
. If it is not an extremely expensive calculation and we have not done any sort of performance checks on how that calculation impacts our app, I'd say don't apply useMemo
just because you feel like it. Premature optimization I'm looking at you.
When to memoize stuff then?
At this point, there are many well-written blog posts and documentation explaining when you should memoize values and callbacks with useCallback
and useMemo
. I'll link some of them below in this post but there is this one by Kent C. Dodds that I highly recommend. Trying to do the same here feels repetitive enough but as I'm mostly a visual learner I've put together some drawings.
Notice in the drawing that Component B is memoized by applying React.memo but in the first scenario an update in Component A still causes a rerender in Component B, even though the props passed do not change at all. Imagine that Component B is expensive and will need to recalculate some stuff when none of its props changed. That is unnecessary.
In the second scenario Component B receives the memoized value for that object and will know that it didn't change at all thus it will not update itself, since it was still wrapped with React.memo
.
It is important to mention that even in those cases you might be better off not always applying `useMemo`. It might be the case that the memory usage associated with `useMemo` does not justify the gain (if any) of not rerendering Component B. 🤷♂️
If you want to see a real example of how these scenarios compare to each other you can check this CodeSandbox with the four possible combinations you can have: sending memoized and non memoized values to either memoized and non memoized components.
I hope this helped enforce what many others have been saying all along about premature optimization and that it also shows how important it is to understand the inners of how such mechanisms work to apply them better.