kentcdodds / ama

Ask me anything!
https://github.com/kentcdodds/ama/issues?q=is%3Aissue+is%3Aclosed
685 stars 75 forks source link

"When to useMemo and useCallback" questions in your Blog post. #718

Closed sukumarvaddi closed 5 years ago

sukumarvaddi commented 5 years ago

Kent, First of all, Great work on your blogs. I learnt ton of stuff from your blogs, egghead and front end master courses. You made me think analytically about React.

In the blog post titled "When to useMemo and useCallback", I am little confused after reading the following lines of code.

"I'd like to mention also that on the second render of the component, the original dispense function gets garbage collected (freeing up memory space) and then a new one is created. However with useCallback the original dispense function wont get garbage collected and a new one is created, so you're worse-off from a memory perspective as well."

I created a codesandbox by modifying your source code and was trying to verify what you meant in the above lines of code. I always got the same function from React.useCallback and no new function is created which is what i want. Let me know the source code in the sandbox is not reflecting exactly what you meant. Did I not understand your blog post correctly? .

I created another codesandbox trying to interpret your blog post. Let me know if this is what you are implying.

Please clear this.

Ruffeng commented 5 years ago

hi @sukumarvaddi, I saw you replied in a question I posted time ago about React.useMemo/useCallback and its confusion, coming originally from the post of @kentcdodds about when to use it or not.

I really struggled in this topic, and at the end I decided to open a threat on Twitter, tagging Kent and other experienced React devs to figure it out( it was literally taking my sleep this thread). I'll try to explain it in my words, in a short way. I'll take one of the examples from the article from Kent:

const CountButton = React.memo(function CountButton({onClick, count}) {
  return <button onClick={onClick}>{count}</button>
})
function DualCounter() {
  const [count1, setCount1] = React.useState(0)
  const increment1 = React.useCallback(() => setCount1(c => c + 1), [])
  const [count2, setCount2] = React.useState(0)
  const increment2 = React.useCallback(() => setCount2(c => c + 1), [])
  return (
    <>
      <CountButton count={count1} onClick={increment1} />
      <CountButton count={count2} onClick={increment2} />
    </>
  )
}

Let's focus on DualCounter. Why do we want const increment1 = React.useCallback(() => setCount1(c => c + 1), []) instead of const increment1 = setCount1(c => c + 1)

The key point relies on 'referential identity'. Every time we re-render this component (for whatever reason), this function is being regenerated with the new component const increment1 = setCount1(c => c + 1). More graphic flow App component renders DualCounter that renders CountButton; If App is re-rendered, Dual it is as well and Count as well Every time we re-render, we tend to remove from the memory all other functions and generate new ones. With this principle memo and callback are entering in the game:

DualCounter is being re-rendered because of App, but now we have const increment1 = React.useCallback(() => setCount1(c => c + 1), []) What is happening, is that is going to the exactly the same flow as the previous concept, but will keep in memory as well the value of increment1, which in this example is a function. That means we have right now 2 functions that do exactly the same in memory. Each of them has a unique internal ID object. Thanks to that, React compares if something has changed internally of these 2 functions, and in case it didn't, it will return the old function instead of the new function generated there.

This is how it really works, and now with CountButton we will see clear this benefit. Before we continue, let's define this React.memo. We learnt about the 'referential identity', so this concept is equally applied but for props. So I receive new props, I compare with old props. Something has changed? YES => Then I re-render with the new info: NO => I stay as it is. What's the benefit of this? You can avoid re-render every time you receive exactly the same props.

How we combine these 2 scopes now? Everytime we re-render DualCounter now we will compare const increment1 = React.useCallback(() => setCount1(c => c + 1), []). This function will never change, since it does something very specific and the dependency that we specified [] will run a single time. That means it will ALWAYS return the same function, and with the same function, we want to return always this same internal ID. So we send exactly the same function into CountButton, and as props receive exactly the same as the previous one, ending, as a result, a no re-render for that component. We are saving up to re-render every time we change something from other components. This is the real benefit of using memo/useMemo/useCallback. The low point is clear: You have these 2 functions they are compared. For memo, they will have a result, but for this result, it has to run what's inside these callbacks from useMemo. So if you just put useMemo or useCallback everywhere, you can get weird results ( expecting a re-rendering to show some results but it doesn't) or hosting all time 2 functions for small things that react doesn't matter to re-render.

React is fast, and knows how to deal properly with this re-renders. So probably we are re-rendering a lot of times in our projects, we don't even realize, and we see fast results. So here is the advice: If you really know how it works, you can detect some scenarios where you can optimize( really expensive results or functions). Otherwise, React does its magic and makes it faster for you.

If you want metric results, check examples, where you think, can be optimized, put a burning chart flame from the react browser plugin, and see the rendering time with or without these methods. If it's just a bit faster, it's no worth it to keep applying this approach ( means React is already optimizing and the benefit is very slow)

This is how I understood it, here the twitter thread. @kentcdodds Please feel free to correct some concepts if you think I misunderstood the whole idea.

I hope it helps :)

kentcdodds commented 5 years ago

@Ruffeng has the right idea here.

I wrote this up yesterday. Maybe it'll help: https://kentcdodds.com/blog/fix-the-slow-render-before-you-fix-the-re-render

Good luck!