DEV Community

Cover image for React Performance Booster - Introduction to the useMemo hook
Rasaf Ibrahim
Rasaf Ibrahim

Posted on

React Performance Booster - Introduction to the useMemo hook

Hooks have revolutionized the way developers build React applications, offering a more intuitive and functional approach to state and side effects. Among these hooks, useMemo stands out for optimizing performance by memorizing computed values. Let’s delve into this powerful hook, step by step.

 

📌 Table of Contents

 

What is the useMemo Hook?

 

useMemo is a hook introduced in React 16.8 that allows you to memorize expensive calculations, ensuring they don't get re-executed unless their dependencies change. This can help in optimizing performance, especially in heavy computational scenarios.

 

Why Do We Need useMemo?

 

Imagine a situation where a function re-renders frequently or performs hefty calculations every time a component updates. This can degrade the performance of the application. By using useMemo, you can ensure that the result of an expensive computation is stored and only recalculated if necessary.

 

Syntax of useMemo

 

The useMemo hook has a simple syntax:

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
Enter fullscreen mode Exit fullscreen mode

Here, computeExpensiveValue is a hypothetical function. The function provided to useMemo will only recompute when a or b change.

 

Breaking Down the Syntax

 

  1. Callback function: The first argument to useMemo is a function that computes and returns a value.
  2. Dependency array: The second argument is an array of dependencies. The memorized value will only recompute when any value in this array changes.

 

Basic Example of useMemo

 

Imagine we have a list of books, and we want to filter them based on a user's search query.

Consider this basic example:

function App() {
    const [query, setQuery] = useState('');
    const [books, setBooks] = useState(['Harry Potter', 'Lord of the Rings', 'Hobbit', 'Percy Jackson']);

    const filteredBooks = books.filter(book => book.toLowerCase().includes(query.toLowerCase()));

    return (
        <div>
            <input 
                type="text" 
                value={query} 
                placeholder="Search for a book..."
                onChange={e => setQuery(e.target.value)} 
            />
            <ul>
                {filteredBooks.map(book => (
                    <li key={book}>{book}</li>
                ))}
            </ul>
        </div>
    );
}
Enter fullscreen mode Exit fullscreen mode

In this example, every time the component re-renders, we're filtering the entire books list, even if the books or the query haven't changed.

By using useMemo, we can optimize the filtering:

function App() {
    const [query, setQuery] = useState('');
    const [books, setBooks] = useState(['Harry Potter', 'Lord of the Rings', 'Hobbit', 'Percy Jackson']);

    const filteredBooks = useMemo(() => 
        books.filter(book => book.toLowerCase().includes(query.toLowerCase())),
        [books, query]
    );

    return (
        <div>
            <input 
                type="text" 
                value={query} 
                placeholder="Search for a book..."
                onChange={e => setQuery(e.target.value)} 
            />
            <ul>
                {filteredBooks.map(book => (
                    <li key={book}>{book}</li>
                ))}
            </ul>
        </div>
    );
}
Enter fullscreen mode Exit fullscreen mode

Now, the books are only filtered again if the books array or the query changes. This ensures that we're not doing unnecessary computations when other unrelated parts of the component update.

 

rich-text-editor-for-react npm package

Demo | Documentation

 

Common Mistakes with useMemo

 

  1. Overusing it: Not every value or function in a component needs to be memorized. Use useMemo only when necessary, as it can add unnecessary complexity and overhead.

  2. Forgetting dependencies: Always ensure that every value used inside the useMemo callback is listed in the dependency array. Omitting dependencies can lead to stale values and hard-to-debug issues.

 

When to Use useMemo?

 

  • When you deal with heavy computations.
  • When the same computation is repeated multiple times and returns the same result for the same inputs.
  • When rendering large lists or data grids and you want to avoid unnecessary re-renders.

 

useMemo vs useCallback

 

Both useMemo and useCallback serve to optimize performance, but they are used in slightly different scenarios:

  • useMemo returns a memorized value.
  • useCallback returns a memorized callback.

If you're looking to optimize a function (not its result) to avoid re-renders in child components or re-invocations, then useCallback is your go-to.

 

Wrapping Up

 

The useMemo hook is a powerful tool for optimizing React applications by memorizing expensive computations. By understanding its purpose, syntax, and best practices, developers can build efficient and responsive applications. Remember, while useMemo is beneficial, it should be used judiciously, only when necessary.

 

That's it. 😃 Thanks for reading. 🎉

Top comments (27)

Collapse
 
brense profile image
Rense Bakker

Anything you do in the render function should be memoized, otherwise you risk all kinds of weird behavior in your app, because you're going to cause rerenders that you may not have intended. Unintended rerenders usually have different state than you expect, often forcing you to write null checks to handle that unintended state. If you want to define something that doesnt depend on state, do it OUTSIDE of your component.

Collapse
 
thethirdrace profile image
TheThirdRace

Hard disagree. This is the mistake most devs actually make.

Do not overuse useMemo, only use it when necessary. This article actually nails it perfectly.

I rarely use useMemo and I never have performance problems in the apps I work on. More often than not, if your component re-renders too much, you have an architecture problem. Slapping useMemo would just be a band-aid on the problem, it wouldn't cure it.

Collapse
 
brense profile image
Rense Bakker

React.memo is a band-aid. useMemo is a necessity to ensure your state is consistent between renders. It ensures the state of your component only changes when you actually intend for it to change.

Thread Thread
 
thethirdrace profile image
TheThirdRace

useMemo has its uses, which the article does a very nice job to explain which ones.

What you suggested is everything you should NOT do with useMemo. There is no point to argue against this, useMemo is only there IF you have performance problems, which is extremely rare and more often a sign of a bad component architecture.

I don't know if you're a native English speaker or not, but the words you use have meanings in the context of React. Unfortunately, because the words you use, you seem to mix concepts together and perpetrate the wrong information as a side effect. Take for example your last comment:

useMemo is a necessity to ensure your state is consistent between renders. It ensures the state of your component only changes when you actually intend for it to change.

I'm sorry to have to point it out, but this is 110% wrong. useMemo has nothing to do with state. state is either coming from a useState, useReducer or an external store. useMemo doesn't come near these concepts at any point.

Some people are mixing the concept of state with props which is NOT the same thing at all, your words seem to point out you're making a similar mistake with useMemo.

The truth is, useMemo doesn't play any role with state. It doesn't ensure your state is consistent between renders and it doesn't ensure the state of your component only changes when you actually intend for it to change. Your state is always consistent and is managed by React. The useMemo command will not affect state and the callback function you pass to it should never modify state otherwise you will have an infinite loop.

The use of useMemo is to encapsulate an expansive function so that if the dependencies don't change, it will bypass the expansive function and return a cached value. That's it! Nothing less, nothing more.

Thread Thread
 
brense profile image
Rense Bakker

I'm sorry to say but you are 120% wrong about what application state is. Props are just a means of passing state down to child components. useMemo produces state just like useState or useReducer... I'm entirely lost as to why you would want to claim otherwise...

Thread Thread
 
thethirdrace profile image
TheThirdRace • Edited

It's the very first time you allude to application state.

As I said, the meaning of words is important and using the right words in the right context is important.

When we're talking about React, using the word state means the actual React state concept; what the lib actually tracks internally as state. For which everything I said is 100% correct.

When we're talking about application state in the greater sense of it, then yes what you said makes a lot more sense. I still feel you're bending way too much what application state technically means, but ultimately, I can understand where you're going with it.

It's important to be precise. You can't expect people to connect a very specific concept in React to a much broader sense of the word without giving the correct context 🤷‍♂️

Thread Thread
 
brense profile image
Rense Bakker

State was a concept that existed long before React. The useState hook in itself indicates a point in React where you can manually update the state using the setState method, but that's far from the only place where state gets manipulated in a React app. Because React uses a uni directional data flow, we pass state down through component props, but if you pass props by value (which you do if you dont memoize), React will interpret this as the state for that component having changed, because the diffing that React does, to determine whether it should rerender a particular component only checks for referential equality. That's not a problem if that particular component doesn't do anything interesting, but if there are any side effects in that component, the behaviour of your entire app might start doing really weird things. The biggest problem here is that other devs won't realize that much higher up in the tree something wasn't memoized and that's why they are seeing bugs in the component they just made.

Thread Thread
 
thethirdrace profile image
TheThirdRace

I have no idea why you're trying to explain to me the basics while I'm trying to explain to you the very precise mechanics behind the basics...

Again, your example for the re-rendering has a flaw: props are not state according to how React treat's it. If you had read the article I posted, you'd know a component only re-render if state changes. Pass any object, doesn't have to be a ref, as a prop to a component then change only that prop and React will NOT re-render. This is what I've been pointing out since the start and what you refuse to believe even if I gave your irrefutable proof 🤷‍♂️

At this point, I guess the Internet strangers will be able to make their mind about what I wrote. Let's just agree to disagree and move on...

Thread Thread
 
brense profile image
Rense Bakker

No that's not what you have been saying, what you've been saying is that you don't need to memoize values that you pass as props, which is incorrect and leads to many bugs. What you're saying now is that in the particular case where you pass a ref (from useRef hook) through props that it will not cause rerenders, that is exactly my point. A ref is a memoized object (a reference) that you pass to component props, which means the referential equality check will pass and the component will indeed not rerender.

Thread Thread
 
thethirdrace profile image
TheThirdRace

I have been saying 2 things since we started this argument:

  1. The article is right about when to use useMemo
  2. Your definition of state is flawed; You mix props with state concept, you reference application state in the general sense while using the specific React state wording which will cause confusion, etc.

Now you're mixing what memoization actually means... There's a big difference between memoization and how object reference works. If you remove all the details, they look alike but they use very different approach.

In the very broader sense, what you say is fine if you don't fret the details. Simply try to understand why I'm pointing out what you're saying is NOT technically correct?

Thread Thread
 
brense profile image
Rense Bakker

Props are state... I'm still not sure why you would claim anything else. You've done it over and over again and it is factually wrong. I've tried explaining to you in many different ways how React works and what the word "state" actually means... I am not sure what else to do at this point, except try to stay far away from any code that you write I guess.

Thread Thread
 
gosukiwi profile image
Federico Ramirez

Now I think you are being dense on purpose lol @thethirdrace tried to pinpoint the definition of state multiple times, and you are back saying 'props are state'.

Just let it go. You should not mindlessly use useMemo all the time, the same way that in programming you should just not do X 100% of the time. There's no silver bullet, and if you were supposed to always use useMemo don't you think it would say so in the docs? The docs only explain useMemo as an optimization tool, just like this article describes.

Thread Thread
 
brense profile image
Rense Bakker

Actually it is mentioned in the docs react.dev/reference/react/useMemo#...

What the docs fail to point out though is that you dont know. You dont know if you or someone else is going to use that piece of unmemoized state weeks/months later in another component as a dependency of a hook, or more specifically in a useEffect hook.

This is not a theoretical discussion. Its a very real problem that causes bugs in React code right at this very moment. So yes, after 4 years of React hooks and seeing that this is the cause for 9/10 bugs in React code. I am absolutely convinced that leaving dirty/unmemoized state inside a component should very much be considered a bad practice.

If you can avoid creating bugs in the future, why would you refuse to do it out of some kind of weird conviction? Is it really too much effort to just type:

function SomeComponent({ name }: { name: string }){
  const memoizedState = useMemo(() => ({ foo: `Yay ${name} wont create bugs!` }), [name])
  return <>Do whatever you like with memoizedState</>
}
Enter fullscreen mode Exit fullscreen mode

vs

function SomeComponent({ name }: { name: string }){
  const dirtyState = { foo: `Screw every developer that comes after ${name}` }
  return <>Better hope nobody passes that dirty state here in the future</>
}
Enter fullscreen mode Exit fullscreen mode

Is it hurting you in any way? Because it is hurting me, that I have to keep fixing these kinds of easily avoidable bugs.

Thread Thread
 
thethirdrace profile image
TheThirdRace • Edited

Actually it is mentioned in the docs
react.dev/reference/react/useMemo#...

I read the link and nothing in that section goes beyond an example of how you could memoize the dependency of a hook. It is not mentioned in this section why you'd want to do that, just how to do it if you need it.

But if you zoom out your focus and read the whole page, the usage section mentions the biggest case: skipping expansive recalculations.

And we're commenting here on dev.to on an article that points exactly at this.

Re-rendering is not a problem unless there's an actual noticeable performance problem. Re-rendering is not a bug.

See this excellent article that explains why you should fix the slow render before thinking about fixing re-rendering: kentcdodds.com/blog/fix-the-slow-r...

Also, the following article explains the cost of memoizing everything: kentcdodds.com/blog/usememo-and-us...

Both articles are written by Kent C. Dodds, author of Testing Library and an absolute beast of a teacher.

Also Nadia Makarevich has pretty awesome articles herself. You should read these 2:

I've linked a few sources in this post and the previous ones. You simply refuse to read them and point out to a section of the official documentation that doesn't say what you say it does 🤷‍♂️

Memoization has a cost. It's just a matter of applying it when the cost is less than not using it. It makes total sense, why would you make your app slower and eat memory like crazy if you can avoid it? 🤔

So to answer the question in your previous post is: Yes, it's hurting me, and everyone, to use memoization where it's unnecessary.

Unless there's a real measurable performance problem, what you see as a bug is not a bug.

And I say this as a performance freak. I'm the guy that bothers everyone at work non-stop about performance. I rarely use memoization because we simply rarely need it. I architect the application state, yes I consciously mean application state here, in a way as to not cause those problems. I'm very sorry you have to deal with those kinds of bugs. The solution is a better architecture, not a slower and memory hungry app/site.

Thread Thread
 
brense profile image
Rense Bakker

Memoization has nothing to do with increased memory usage 🤦

Thread Thread
 
brense profile image
Rense Bakker • Edited

Nadia Makarevich argument is: if you have atleast one piece of dirty state in your component, go ahead and create more dirty state... What kind of attitude is that? Her example is dirty:

function DirtyComponent(){
  return <OtherComponent someProp={[1, 2, 3]} /> // someProp will always contain a new value
}
Enter fullscreen mode Exit fullscreen mode

It's easily fixable, do this instead:

const neverChangingArray = [1, 2, 3] // <-- this is still application state

function CleanComponent(){
  return <OtherComponent someProp={neverChangingArray} />
}
Enter fullscreen mode Exit fullscreen mode

Or if you need other state, memoize:

function CleanComponent({ someItem }: { someItem: string }){ // someItem is application state, passed down via props
  const noChangeBetweenRenders = useMemo(() => [1, 2, someItem], [someItem])
  return <OtherComponent someProp={noChangeBetweenRenders} />
}
Enter fullscreen mode Exit fullscreen mode

If you dont and OtherComponent has a useEffect (literally any side effect) that depends on your dirty state, you just screwed someone in the future:

function OtherComponent({ items }: { items: string[] }){ // items is applications state, passed down via props
  useEffect(() => {
    // trigger any side effect when items change
    fetch('/my/api').then(() => {
      // in reality always executes on every render because items always change.
    })
  }, [items])

  return <>foobar</>
}
Enter fullscreen mode Exit fullscreen mode

This is a simple example, in reality the dirty state that you produce gets passed down through many levels of the component tree until nobody knows what is dirty and what is clean. Pretty sure that Kent C. Dodds and hopefully also Nadia Makarevich have realized this by now and changed their ways.

Collapse
 
rajaerobinson profile image
Rajae Robinson

Very informative post. I definitely agree to avoid overusing useMemo. One way to figure out whether or not a process needs to be optimized is to use React's Profiler to check for performance bottlenecks.

Collapse
 
rasaf_ibrahim profile image
Rasaf Ibrahim

Thanks for sharing an informative article.

Collapse
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
rasaf_ibrahim profile image
Rasaf Ibrahim

Thanks for your feedback! Glad you found the examples helpful.

Collapse
 
gunslingor profile image
gunslingor

Isn't useEffect basically the same thing since it's watching things for changes in the second arg?

Collapse
 
brense profile image
Rense Bakker

useMemo is executed as part of the render cycle and is resolved before rendering starts. useEffect is executed as part of the render cycle as well, but rendering will not wait for the result. useEffect is basically a backdoor to the world outside of the React scope. It allows you to handle side effects and produce a new state which wil cause a new rerender. useMemo will not cause a rerender.

Collapse
 
thethirdrace profile image
TheThirdRace

The truth is, "render cycle" could mean many things to different devs. It's too easy to not quite catch the difference between render and render cycle.

It would be more appropriate to describe both as below...

useEffect

useEffect is a callback function called asynchronously after the render.

It can be called before or after the browser painting phase, there's no guarantee when it will run beside it's gonna be after the render.

It should be used to synchronize data with external system, the function will even be called twice in a row in development to force you to make the callback function idempotent.

It doesn't return any value to the render, it's "disconnected" in the same manner a setTimeout would be.

useMemo

useMemo will be called during the render, it's a synchronous function just like useState.

It will directly return a value you can assign to a variable which the render will use immediately.

Thread Thread
 
brense profile image
Rense Bakker

Rendering is the act of updating the UI with the current state of the application. React apps do this in cycles. Each cycle follows a particular order of rules which are meant to ensure the correctness of the state of the app. In other words, you can only update state once between each render cycle. What useMemo does is return a state value based on other state properties in the current render cycle. The render cycle waits for useMemo to return a result before it continues with the next steps. useEffect, like useMemo (and other hooks) is executed during the render cycle, but you cannot change the state of the current render cycle inside the useEffect. Changes to the state inside useEffect will trigger another render cycle with a new state once the current cycle completes.

Thread Thread
 
thethirdrace profile image
TheThirdRace

To clear up any misunderstandings in what render is, I highly suggest these readings:

Both articles explains pretty well what is a render and what are the misconceptions perpetrated by the majority of React developers.

As for how you describe useMemo in your comment, you're close but you're also mixing concepts. See my follow up comment on useMemo to understand why I say you're mixing concepts and why useMemo has nothing to do with state

Thread Thread
 
brense profile image
Rense Bakker

I think you're confusing application state and literal state from the useState hook 🤷

Thread Thread
 
thethirdrace profile image
TheThirdRace

So you ARE able to make the difference between the two...

You're just too set in your way to accept that saying state in a React context doesn't necessarily means application state...

Duly noted 👍