React Performance

useMemo & useCallback

Steve Kinney

Steve Kinney

React Performance

Check out a free preview of the full React Performance course

The "useMemo & useCallback" Lesson is part of the full, React Performance course featured in this preview video. Here's what you'd learn in this lesson:

Steve demonstrates using the React hooks useMemo to cache the result of a calculation and useCallback to cache a function definition between re-renders. Student questions regarding utilizing useCallback instead of useMemo creating new functions and if the interest in the execution of the body is why useCallback is used are also covered in this segment.


Transcript from the "useMemo & useCallback" Lesson

>> So like these are different values are stored. So it compares this function to another function, different memory addresses. We're assigning it each way through. Okay, the rules of react, memo apply, everything works, right. It's like the code works just not as you expected, but it works. So we clearly need like a few more tools in order to handle this, right.

And react has those tools for us because this is obviously like, why would react memo exist if you use hooks and thereby? It wasn't a thing. Interestingly, this is not as much of an issue with classes, because methods are the same. They're the same function attached to that object on the prototype, right.

So, point four class-based components, that methods are the same object in memory every time, you're not redefining them. Unless you do some of the modern stuff, where you're gonna assign it to a class variable. Unless you do the kind of class components I was writing before I switched over to functional components.

Where I was just opting out of most of the goodness anyway. But generally speaking react has some tools to help us with this. UseMemo not to be confused with react now and useCallback, right. You've probably heard of these before, useMemo is a way to say use effects friend.

It's like use effect and use callback are hanging out with each other. It's saying like, hey, generate random number, that was an expensive function, right. And let's say it took some arguments. If all these other things that you rely on are the same, don't run. If you have a pure function that the idea of a pure function is like.

If the same inputs go in the same value comes out, right? And you've seen these are the same inputs you saw as last time. We don't need running again, we know how the story ends, right. So it's great for any kind of expensive value that you might be running.

You can even use it or on components and stuff like that to like, okay, we've run out we've got the component rendered. There's a bunch of interesting things you can do with it. Probably more commonly is useCallback and useCallback is, I'm gonna hand you this function. It's clearly got set items from a closure scope, right.

And you definitely need to know about that. And items more importantly, cuz if you're gonna push onto the array. If you have an old array. You're gonna push on to an old array and now you state matters it's gonna get all weird. So all the things that you rely on are the same.

Don't with function held on last time. This is kind of when we saw you say initializer function on the floor or previous values on the floor. These data structures in that react fibres are familiar with what happened last time, even if you're not. As you say if I just looked at all the things you said that this thing cares about, they're the same.

So I'll give you back the one from last time, right. And that will actually get you the same memory reference to the function and then make all of this work. So we can go ahead, and this becomes basically as simple as, UseCallback, we can wrap the function and we're given that dependency list, really the only one we care about is items.

And you'll notice that like, yes, lint doesn't yell at me for not having said items. Would anyone like to venture a guess why? Yeah.
>> Just because it's still within the scope, right.
>> Yesish, because this is just setting it, react is already actually using effectively. UseCallback to give me the same version every time.

This one is not changing. I am getting a same memory reference every time to set item. But items, I'm literally redefining it all the time. The only thing in this app is items so by definition, those are regenerating every time. But this is saying like unless items has changed.

I don't need you worrying about this, right. And so useCallback will kind of give you the same thing as long as this hasn't changed. Now, like we said before, most of the time that is like the thing that changes in this application like I could pull back up.

The draft item, I could go put in that ridiculous code that I had. This is mostly to kind of show you the demonstration of why it didn't work and how it can work. But it is the kind of solution for this problem and like for this one, especially for functions, which don't change.

This is a pretty safe and reasonable thing to do especially if you are creating custom hooks yourself, right. In the same way that set items is static every time. I would expect that if I got a some kind of setter function. From either a third party hook or one that we made the same principle would be there, right.

This is a thing that yeah, we're binding them together. Awesome, and I'll show you how to not do this in a second too. But for the most part like this I think is under the things where it's like, I think this is definitely a fair and safe one to do.

Cuz you're not getting any benefits by whipping up a new function every time either. So, gotta give the list. Items. And then there's a need be like I should use use memo on these. It's tricky because the only thing that's out that could ever change is the items and so if there were more things and this was expensive, yeah, totally go for it right on.

But doing that, when you get no benefit is just pointless, right, And so some of this stuff is like. If the answer was always yes, it would happen for you, like they would just do it. Yes, Mark.
>> So you recommend using, useCallback instead of useMemo due to creating of the new functions?

>> So you use them for different cases. If I wanted the value to be persisted unless things change I use useMemo. If I want a function that's the same reference every time I useCallback. UseCallback is for when I need a function. UseMemo is for when I need a value.

It's just basically joining a value or a function if I use useMemo here I would get the result of this all right. Now the function itself that I wanna pass down to items, so the items are the ones that will pass in the ID to remove them, yes Mark,

>> Will memo work with nested prop changes.
>> I think it's still a shallow compare. I'm trying to remember if it takes another argument I don't think it does. It's like in the same way you saw that react that memo took it like is now it's I think it's just looking at the highest level.

But you could say if you only cared about one property of that. It's like the nice part is that like EsLint will do both for you. If you ever set up it will both yell at you when you're missing something. I think, if I'm not mistaken sometimes, yell at you if you pass something that's not gonna change.

I have been yelled at it by before, but will tell you. But generally speaking, the things that you basically, you can have static analysis to it or you can look at your own code. What are the things that you're calling. That are coming from outside the closure scope.

So obviously, this exists statically cuz I'm importing it. These two arguments, it's really items and set items that could be possibly, I think if I put like update items in here I'll get yelled at. Yeah, you can see React to reduce callback has an unnecessary dependency. Update item either exclude it or remove the dependency array.

Other out of scope values like aren't valid dependencies cuz doesn't rerender the component. So you get a sense of what the right things are in that sense. You can have the tooling help you. You can put it through all these. Should have kept that set up just function there but we'll do it in a second.

But these are now every unless items change which is a thing that happens a lot In this particular if I put something else in here, like a timer or something, that doesn't change it. These will stay the same and they won't trigger a reminder, right yeah.
>> So, definitely speaking, if the function does not have any effect.

And it definitely is a pure function and you're interested only in the output you useMemo because you don't even need to run it anymore.
>> Mm-hm.
>> And in cases like this, when you have some sort of effect where you're in state or doing anything else. You useCallback because you're interested in the execution of the body.

>> Yep, exactly right. And the important thing to note is that like, this is only if I want these helpers. If I was comfortable passing down set items to the entire chain or dispatch on a reducer. Like I said before those are the same value every time. It's only because I'm whipping these functions up on the fly up here.

Cuz I need to for some reason maybe there's some information that they need up here something along those lines. Then I do need to say unless it as long as the things you rely on, as long as the reason why you're up here hasn't changed be the same one, but set items itself dispatch on use reducer stuff along those lines.

Those are static every time, they are the same reference like this has already been done to them. Cuz they're just updating that data structure inside of React. They have access to the full history of things. They know everything. They don't necessarily care about what's in this current closure scope.

But if you define the function here, everything in this scope is accessible to it. And if those variables have changed, you're gonna get an old version of the function that knows about an old world. When Hooks came out, it's like you traded all of your class hierarchy problems for closure problems, right?

It's so much simpler. Now you have a new set of things a worker, which is how closures work in JavaScript. And will occasionally bite you but this will always be whatever items was at the time it was written, not what it is eventually, right. So this says if items changes throw the old one out.

And get me one that knows about what the current latest and greatest version of items is, right. And since we immutably changed things if we had mutable state that wouldn't be the same thing that we were modifying. But like we've long since either discarded it or actually garbage collected it.

Not because there's a closure scope, but generally speaking, it will be an old version of that variable. So this is a way to say that's totally cool because I wanna React Memo to work, but at the same time. If that becomes out of date, then go get me a fresh one, all right.

Which way you do need to pass that new thing down, and have all that kind of work. And this is true even with the context API, right. Because the contact API is just basically pulling this out of the function. And letting places hook into it but all the same rules apply in this case as well.

But this is a powerful way to get functions that we find that will then work us through React Memo. I should put back that silly function that I wrote earlier. Did I just saved it? No, so, we'll just say Cool. SetTimeout, For a second here we'll flip over, And just so you make sure we're keeping ourselves honest.

The application re-renders, right. You saw the big box around the entire thing. Because that's where we're changing that state. Now that should, but nothing else re-rendered. Right, because those are still the same update and remove. They're getting passed all the way through. Like I said before, if you just wanted to pass setItems all the way through and then figure out everything else down at the bottom, that also works and will save you the useCallbacks, right.

You can just import all these functions in all the different places that you need to. And I think that works if you're just trying to get it down to one grandchild, great grandchild component. If you find yourself needing it at various places along the way, now you're just importing stuff all over the place.

Now it becomes like, is it performance? Sure, is it code that you can maintain? No, right, one of my hot takes is that backend services when they don't scale it's because the database falls over under load. And stuff like that a lot of times with front end code bases we do manage just like tons and tons of state.

It's that like all of a sudden when we get asked for a very simple feature we tell them three months. It's because sometimes we over optimized for different things. And now maintaining your own code base is borderline impossible, right. And so sometimes it's about thinking about those trade offs in that sense as well.

But yeah, these are static but any function that you make, along the way, if you don't deal with it. Or either rapid or then do these techniques where you're just passing thing and then importing the other pieces where you need it. You will end up breaking your own React memo, and then also paying the cost for that react revenue in the first place.

It's like you get the worst of both worlds. You're now doing the comparison all the time. And it's always failing, right. You might as well just taking it would have been more performant without the React memo on it at that point, right. Because like it would have been re-rendering anyway.

And so it's like that's where sometimes measuring is important which is sure. Try it out, absolutely put it on there. Try to guess a future you is gonna have to deal with, right, because you can't measure future years code right now. But then make sure that you're actually getting a gain or make sure they're stopping anything.

If you just blindly take it as advice. And you just go for it you could literally be making the problem maybe not dramatically worse, but over time. Incrementally worse, right until you then have to. And it's a lot harder to triage why a bunch of light implicitly memorizing stuff is slowing stuff down.

Than just seeing a prop shoot all the way down through it, so you get a harder bug to solve. Also, the idea of caching stuff when it shouldn't be cast. Every year you take on not only performance risk, but you also take on a little bit of a weirdness cache invalidation problem mess.

Where you now have a weird bug, right. You're playing with a little bit of fire. That's why component hierarchies first, right stuff like memo lane, those things are there for a reason, right. Absolutely use them, but we try to do our due diligence as we're going through. And we try to understand the problem and see what our options are that are available to us.

Awesome, so generally speaking, let's look at, if I take out my silly code, let's use the app a little bit. There's still gonna be a decent amount of cuz there's really only items in this particular one. We'll play with some other ones in a second. But like if I edit one, it's a lot of things like new item name is that one rendering.

That's interesting. Do we put it on that one? Cuz that one should not, right? That's when we would like to see stop at this point. That's interesting. I'm curious why. it's getting an item to ever useCallback on that one. That one should stop. Let's take a look Interesting, let's make sure that's actually happening.

Well, I'll take a look at the second. That's true. All right, so we saw header. It might just been the one above and the one below it and the shell around the side. I can tell that I saw the green thing, I was like, is that rendering? It's clearly not.

The two lists which are theoretically the same list before they're rendered. And let's talk about that a little bit too, cuz I think there's an interesting point there that is a tricky problem to solve. The only things that are item was prompting to update removed because items change.

Everything is actually at this moment working as expected.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now