DEV Community

Cover image for Introduction to React.memo, useMemo and useCallback
TrinhDinhHuy
TrinhDinhHuy

Posted on • Updated on

Introduction to React.memo, useMemo and useCallback

Prerequisite: Basic knowledge about React

Cover photo by Danika Perkinson on Unsplash

When I start to write this blog, I ask myself whether I should only talk about the differences between useMemo and useCallback because they are React Hooks while React.memo is not. Eventually, I decided to include React.memo as well since on the one hand the word memo in both terminologies might sound a bit confusing for people. On the other hand, it's all about React optimization 😁

optimization

1. What is React.memo

If you are familiar with React.PureComponent then React.memo is quite straightforward as it is exactly similar to React.PureComponent. We use React.PureComponent with class component while React.memo works with functional components 👌

Let's take a look at our example to see how it works. Codesandbox

Note: All the examples below are used only to express the main ideas. In reality, we don't need optimization in such simple cases

const App = () => {
    const [count1, setCount1] = React.useState(0)
    const [count2, setCount2] = React.useState(0)

    const increaseCounter1 = () => {
        setCount1(count1 => count1 + 1)
    }

    return (
        <>
            <button onClick={increaseCounter1}>Increase counter 1</button>
            <Counter value={count1}>Counter 1</Counter>
            <Counter value={count2}>Coutner 2</Counter>
        </>
    )
}
Enter fullscreen mode Exit fullscreen mode
const Counter = ({value, children}) => {
    console.log('Render: ', children)

    return (
        <div>
            {children}: {value}
        </div>
    )
}

export default Counter
Enter fullscreen mode Exit fullscreen mode

Every time the user clicks on the button, the state of count1 changes causing the App to rerender both counters which is known as unnecessary re-render. However, we expect only the counter1 to be rerendered since nothing has changed with the counter2. In reality, both counters get rerendered.

How can we address this issue? 🤔 Well, React.memo is our answer. All we need to do is to wrap our Counter component within React.memo

const Counter = ({value, children}) => {
    console.log('Render: ', children)

    return (
        <div>
            {children}: {value}
        </div>
    )
}

export default React.memo(Counter)
Enter fullscreen mode Exit fullscreen mode

By default, React.memo will compare all props passed to the component by referential equality. If these props are unchanged, React.memo will reuse the last rendered result, therefore, it prevents the component from being rerendered. In our example, React.memo will check if there are any changes with the value and children props since the last render. Since our button only changes the value of the counter1, React.memo will prevent the counter2 from being rerendered. 😎💪

We can also override the default comparison of React.memo by providing a custom comparison function as the second argument.

From React.memo documentation

const Counter = () => {

   const areEqual = (prevProps, nextProps) => {
     /*
     return true if passing nextProps to render would return
     the same result as passing prevProps to render,
     otherwise return false
     */
   } 
}

export default React.memo(Counter, areEqual)
Enter fullscreen mode Exit fullscreen mode

2. useMemo and useCallback

I will start with the documentation

useMemo returns a memoized value

React.useMemo(() => {
  fooFunction()
}, [dependencies])
Enter fullscreen mode Exit fullscreen mode

useCallback returns a memoized callback

React.useCallback(() => {
  fooFunction()
}, [dependencies])
Enter fullscreen mode Exit fullscreen mode

💪 Let's break it down together

Both React.useMemo and React.useCallback receives a function as its first argument and a dependencies array as the second one. The hook will return a new value only when one of the dependencies value changes (referential equality). The main difference is that React.useMemo will call the fooFunction and return its result while React.useCallback will return the fooFunction without calling it.

😫 Example please codesandbox

const App = () => {
    const fooFunction = () => {
        return 'Foo is just Food without D'
    }

    const useMemoResult = React.useMemo(fooFunction, [])
    const useCallbackResult = React.useCallback(fooFunction, [])

    console.log('useMemoResult: ', useMemoResult)
    console.log('useCallbackResult: ', useCallbackResult)

    return <p>Foo is just food without D</p>
}
Enter fullscreen mode Exit fullscreen mode

If you run the code and take a look at your console, not refrigerator, you can see the following output

use-memo-and-callback-result

React.useMemo runs the fooFunction which returns a string Foo is just Food without D while React.useCallback just returns a fooFunction without calling it

🤩 Got it. But how does it work in React?

🍀 useMemo

Normally we could use React.useMemo when we compute expensive value that we don't want to compute it again and again when the component is rerendered

const Me = ({girlFriendWords}) => {

    // Provided that girlFriendWords is a string

    const myReply = decideWhatToSay (girlFriendWords)

    return <p>{myReply}</p>
}
Enter fullscreen mode Exit fullscreen mode

Imagine that it takes full of my energy to calculate myReply value and what if I have to do it again and again (recalculate) when my girl friend says something (rerender) 🤐

🔥 React.useMemo comes to rescue

const Me = ({girlFriendWords}) => {

    // Provided that girlFriendWords is a string

    const myReply = React.useMemo(() => decideWhatToSay (girlFriendWords), [girlFriendWords])

    return <p>{myReply}</p>
}
Enter fullscreen mode Exit fullscreen mode

Thanks to React.useMemo, I couldn't have finished this blog without you 💑

React.useMemo takes [girlFriendWords] as its dependencies array which means that it will only run decideWhatToSay function when girlFriendWords value changes. I don't have to think twice to reply when my girlfriend says the same thing. Optimization here 🎉🍀💐

girlfriend-optimization

🍀 useCallback

Enough relationship story, let's get back to our Counter example. Let's tweak the example a little bit, our counter now also receives onClick function as a prop. Could you guess whether our Counter2 component will be rerendered when count1 value change

const App = () => {
    const [count1, setCount1] = React.useState(0)
    const [count2, setCount2] = React.useState(0)

    const increaseCounter1 = () => {
        setCount1(count1 => count1 + 1)
    }

    const increaseCounter2 = () => {
            setCount1(count2 => count1 + 1)
    }

    return (
        <>
            <Counter value={count1} onClick={increaseCounter1}>Counter 1</Counter>
            <Counter value={count2} onClick={increaseCounter2}>Coutner 2</Counter>
        </>
    )
}
Enter fullscreen mode Exit fullscreen mode
const Counter = ({value, children, onClick}) => {
    console.log('Render: ', children)

    return (
        <Button onClick={}>
            {children}: {value}
        </div>
    )
}

export default React.memo(Counter)
Enter fullscreen mode Exit fullscreen mode

The answer is YES 😮.

Even when we use React.memo, the counter2 component is still rerendered when only the count1 changes because React.memo uses reference equality to prevent unnecessary renders. However, when App is rerendered, the increaseCounter2 is recreated, therefore, the onClick props passed into the Counter component is different every time which causes the component to be rerendered. The easy way to avoid this issue is to prevent the increaseCounter2 function from being recreated when the App is rerendered.

We make use of React.useCallback to do this

const App = () => {
    const [count1, setCount1] = React.useState(0)
    const [count2, setCount2] = React.useState(0)

    const increaseCounter1 = React.useCallback(() => {
        setCount1(count1 => count1 + 1)
    }, [])

    const increaseCounter2 = React.useCallback(() => {
            setCount2(count2 => count1 + 1)
    }, [])

    return (
        <>
            <Counter value={count1} onClick={increaseCounter1}>Counter 1</Counter>
            <Counter value={count2} onClick={increaseCounter2}>Coutner 2</Counter>
        </>
    )
}
Enter fullscreen mode Exit fullscreen mode

Take a look at the dependencies array, it's empty because I want to create these functions only once. In this way, the onClick props passed to the Counter component is always the same

3. Conclusion:

  • 🚀 We should not optimize unnecessary rerenders without measuring the cost first. Optimization always comes with a cost
  • 🚀 React.memo is similar to React.PureComponent except the fact that it is used for functional component while React.PureComponent is used only in class component
  • 🚀 React.useMemo returns a memoized value while React.useCallback return a memoized callback

Here are some good resources for you:

🙏 💪 Thanks for reading!

I would love to hear your ideas and feedback. Feel free to comment below!

✍️ Written by

Huy Trinh 🔥 🎩 ♥️ ♠️ ♦️ ♣️ 🤓

Software developer | Magic lover

Say Hello 👋 on

Github

LinkedIn

Medium

Oldest comments (14)

Collapse
 
redraushan profile image
Raushan Sharma

Hi TrinhDinhHuy, well put buddy, it helped me to clear out a few doubts that I had.

Moreover, I guess there's a type in the following code:

const myReply = React.memo(() => decideWhatToSay (girlFriendWords), [girlFriendWords])

I feel, instead of React.memo, it should be React.useMemo.

Keep writing!

Collapse
 
dinhhuyams profile image
TrinhDinhHuy

Thank you! I updated it

Collapse
 
netochaves profile image
Neto Chaves

Hi TrinhDinhHuy great post, really helped me to understand about memoization on react.

Just a typo, on Counter component that u render a button, you're closing this buttton with div and not a button tag.

Keep writing!

Collapse
 
anhnguyenvanagilityio profile image
anh.nguyenvan • Edited

Thanks for shared, it's very helpful, but i have a case, can you please help me clear
blog.axlight.com/posts/four-patter...

const App = props => {
const [stateA, dispatchA] = useReducer(reducerA, initialStateA);
const [stateB, dispatchB] = useReducer(reducerB, initialStateB);
const [stateC, dispatchC] = useReducer(reducerC, initialStateC);
const valueA = useMemo(() => [stateA, dispatchA], [stateA]);
const valueB = useMemo(() => [stateB, dispatchB], [stateB]);
const valueC = useMemo(() => [stateC, dispatchC], [stateC]);
return (



{props.children}



);
};
how the useMemo work? many thanks

Collapse
 
kibobishtrudelz profile image
Petar Kolev

React.memo() is a HOC. You can use it with class components as well with functional ones. It's not true that it comes handy only on functional components. useMemo i a hook and it goes only to functional components.

Collapse
 
iarkovenko profile image
Iarkovenko

Why Counter doesn't update its own value after we update count1 ? increaseCounter2 remember count1 value equal 1 after first update using increaseCounter2

Collapse
 
machy44 profile image
machy44

I believe you figured out why in meantime but I will answer anyway.
const increaseCounter2 = React.useCallback(() => {
setCount2(count2 => count1 + 1)
}, [])
Because when state is set author uses count1 + 1. I believe he wanted to write count2 + 1 and that this is syntax error.

Collapse
 
ardyfeb profile image
Ardy Febriansyah

Will useCallback useless if passed to children that not using React.memo?

Collapse
 
jvinhit profile image
Vinh Nguyen

wow :D thankiu "Bác Huy"

Collapse
 
thanhlongnguyen140499 profile image
Nguyễn Thanh Long

Good Article! Thank you, Huy!

Collapse
 
dinhhuyams profile image
TrinhDinhHuy

You are welcome

Collapse
 
trangchongcheng profile image
cucheng

Hi Huy,
Do you think about React.memo must used together with useCallback?
Because the component wrap by React.memo always re-render with props is reference type.

Collapse
 
dinhhuyams profile image
TrinhDinhHuy

If you pass something with reference type such as function then YES we can use useCallback to prevent the re-render. Otherwise it's not necessary if you pass primitive values (boolean, string, ..)

Collapse
 
gaurbprajapati profile image
gaurbprajapati

One of the great post wow