Current section: Latest Ref 30 exercises
solution

Latest Ref

Transcript

00:00 So, the number of co-changes is actually pretty small, but we're going to talk about each one of them so that we understand what's going on. So, here's our debounce function. It's a pretty simple version of a debounce, but it's going to take any number of arguments, or rather, it's going to take

00:19 a callback that accepts any number of arguments. We don't really care about what those arguments are, we don't care about what it returns, any of that. Our debounce function takes a function that resembles this, and also a delay, and then it creates a timer and returns a function.

00:37 This is the function that ultimately is going to get called. So, this is our debounce function. It takes all the same parameters as the debounce function callback does, and so when it's called, it's going to clear a previous timer. It's like, oh, the last time it was called, we don't need to call it anymore,

00:57 and it's going to set a new timer that, when complete, will call the function with all the given args. So, that's the basic idea of debounce. It is a pretty naive implementation, but it works really quite well for simple use cases. So, our useDebounce hook,

01:15 which we want to have everything hook into a single, or have it hook into React's life cycles for components and all of that for memoization purposes, and particularly if you want to call something in a use effect or something like that,

01:35 you want to do something like this. So, our useDebounce is going to have, actually, kind of a similar API. It's going to take a callback and a delay, and then here we're memoizing the function that's returned. So, this function right here is what's getting memoized because we want to be able to use that in a dependency array for other things.

01:54 And so, we're saying, hey, useMemo, here's the way that you get a memoized version of our debounce function. So, here's the callback I want you to call after this delay, but because useMemo requires this dependency array, because we want to know,

02:12 okay, when do I get a new version of the debounce function? Because of this, we have to create a new debounce function every single time the callback changes. And unfortunately, the way that we're using this right now, this callback changes every render. So, if we update the step or we update the count,

02:29 then, or if this app has a parent that's re-rendering and re-renders the app, then we're going to be updating this increment, which means our debounce is actually not going to work quite right because we're going to be making a bunch of different versions of this debounce function. And what that will mean is if it's called again,

02:49 then we'll have a totally separate timer, and so it's going to get called automatically. So, it'll just get called more than it should. It'd be like scanning our groceries a whole bunch of times. Basically, the debounce isn't doing anything other than delaying things getting called for the certain amount of milliseconds,

03:09 which is not what we want. We want it to only get called the one time. Okay, so with that information then, what we want to do, and actually here, we can swap this out. We'll see this doesn't actually solve anything. We're going to actually, this is use callback. There we go. Bring that in from React.

03:30 So, if we say, okay, well, let's just use callback this thing and pass that in, and so then this will be memoized and everything should be good. But unfortunately, that is not going to work because if I do this and increment to two, then boom, we get incremented by one.

03:47 And the reason is that if we follow the code flow, we create this increment function that's been callbacked, and so it's going to be memoized. If other things get changed, then it should be the same. We pass that to useDebounce, useDebounce takes callback. That goes into our useMemo. The debounce function is created,

04:08 and then we're all ready to go. Then we click on the button, and so we call this function right here on that debounce function. We're waiting for this timeout to finish before we call this function. So, we're waiting for 3,000 milliseconds, but soon thereafter, we increment the step,

04:26 and so we're going to trigger a re-render. That's going to update our increment function because it depends on the step. We pass that into useDebounce, and so with useMemo, because the callback changed, because the step changed, we end up creating a brand new debounce function,

04:46 and that new debounce function is working great, but it is referencing, this one's referencing the correct step, but it's not referencing the latest step, and so the one that finally does get called has a reference to the old step. This is our original debounce function. It's going to be a different one,

05:07 and so this is going to cause all sorts of problems. It's not going to work. And aside from that, it's not even a very nice API. Like, you don't want to have a hook that says, oh yeah, don't forget, you have to memoize it. That's not necessarily a bad thing all the time. Sometimes you do want to say, yeah, you need to memoize this,

05:25 but often, requiring memoization is not fun at all. So, that is why we're going to be using the latest ref because we want the behavior that we're going to get from the latest ref pattern. So here, we're going to get our latest ref callback, and let's bring in useRef from React,

05:45 and then we'll have useEffect from React here to update the latest ref callback, and then down here, instead of referencing the callback, we're actually going to make a new function that takes any number of arguments. Actually, you know what?

06:02 Spoiler alert, we're going to just pass the latest ref callback.current, and we'll see why this isn't going to work here in just a second. Okay, so now we're passing the latest ref callback current, and we no longer need to pass that in the dependency array,

06:19 and so, yeah, let's try this out now. So if I click on this, I increment one, and it should now increment to two, but it only increments to one, and the reason is that we created the debounce function to be what the latest callback ref.current was at the time we created the debounce function,

06:39 which was our initial increment function, which means that the step was set to this, and so what's interesting is actually, I can change this step however much I want, and if I increment this, it's always only going to be incremented by one because we're still referencing the original increment function,

06:58 which had step set to one at that time. So that's why we need to set this to be a function that accepts any number of args and then just forwards those onto the latest callback because now it's a function that will call the latest version rather than just being what the latest version was

07:18 at the time that the debounce was created. Like I said, not a lot of lines of code changed, but if I increment this and increment the step, then this should go to two because we're calling the latest version, which has its step, the version of that function has a step which is set to two. If I click this again and increment this a bunch,

07:38 then it will go up to 12. It just says, we desired. So that is the latest ref pattern. I hope you had an awesome time implementing that. It literally was like three lines of code and then just updating references, and then of course, yeah, creating this function inside of here

07:56 just by the nature of how debounces work. Just making sure that you're always calling or referencing the latest version whenever it needs to be referenced.