Current section: Custom Hooks 36 exercises
lesson

Intro to Custom Hooks

Loading lesson

Transcript

00:00 All right, you might recall when we were talking about custom components, they're simply functions that return something that React can render. Custom hooks are similar, they're just functions. But in this case, they're functions that use other hooks.

00:16 That is the definition of a custom hook. Whether that other hook be a React hook that's built into React, or if it's some other custom hook. So that's the entire rule of what makes a custom hook.

00:35 Conventionally, they always are prefixed with use, and that can be helpful for linting plugins and things like that. But that is the idea, that hooks are just functions that use other hooks. And they're a really, really powerful mechanism for encapsulating logic.

00:55 So here we have this useCount. We've got our count state and our increments, and we can return that. And now our counter can use useCount. And all the implementation details of that state management is inside the useCount hook. So there's a whole bunch of other aspects of making custom hooks

01:13 that involves things getting more complex faster than you might want them to. So the example is that maybe you need to pass a function or some other value to a hook because it's going to use that inside of a use effect.

01:32 And now you need to include that thing inside of your dependency array. And so I find that as I'm making custom hooks, I typically have to do a lot more or have a lot more thought around what happens as far as my dependency array is concerned.

01:50 Which is why I typically don't like just creating custom hooks just for the fun of it. Which is actually the same way that I am about regular abstractions that I'm making. If I don't need to reuse it and it's not clobbering up my code too much,

02:08 I guess everybody's got their own threshold for that. But I like to just inline as much as I possibly can. I don't mind long functions that have understandable chunks in them. I don't think that's a problem. Anyway, we do need to talk about this dependency array right here. So here we're calling useCount and we're getting increment back.

02:28 And so if I wanted to increment on an interval, then that increment function is going to be passed in the dependency array here. But that increment function is going to be different every time we render, right? Because when increment is called, it's going to call setCount.

02:45 That'll trigger a re-render of the component that's using our hook. And so then all this code is going to run again. And now increment is a new function. So because it's a new function, this use effect is going to run again.

02:59 So effectively what's going to happen here is when this ticks, when we call increment, the setInterval is going to get cleared because the increment function changed and we're going to create a new interval. So it'll happen so fast that it will probably technically still work.

03:17 But it's, yeah, certainly not optimal to clear the interval and set a new one every single time. So what you have to do is memoize increment using a special hook called useCallback. Let's talk about memoization first, though.

03:34 In general, here is an example of memoization. If you want to dive really deep into memoization, I've actually got a great talk called Caching for Cache on Epic Web Dev. You can go and watch this, and I dive pretty deep into what caching is. So that should help you out a little bit.

03:53 But here's a basic quick run-through. So we have this addOne function, and we take a number and we add one to it. But before we do that, we're going to check this values object. And if we already have added one to the number two or three or whatever,

04:12 then that should appear inside of this values object. And if that's the case, then we already computed the value of what two or three plus one is going to be. And that is contained inside of this object.

04:27 So rather than performing this calculation again, we're going to simply return the value. That's the basic idea behind memoization. It's just take a computation, store it somewhere. And then anytime you need to perform that computation again,

04:43 you can look there and see, did we already do this and return that instead? Of course, this would be more, it would be actually worse for performance to do things this way because the computers are pretty good at adding numbers together. So just adding a level of caching isn't going to necessarily make things faster.

05:02 But it could, and there are certain scenarios where this is useful. In particular, it's useful for performance in some cases, like if this computation is really expensive. But it's also useful for referential equality, which is what we need right here. So we need this increment to be the same function every time.

05:20 Even though we're creating a new one every time, we need to instead have this be the same function every time. So how does that work? Well, referential equality. Here's an example of this. If we had a class called dog and we made two dogs that have the same name, those objects are going to be different. Even though they may have the exact same structure,

05:39 they're the same class, all of that, they're still going to be different. However, if we made a getDog function that could create a dog and then had a cache for that dog, then we could say getDogSam and getDogSam and those two would be the same because we cached it in this dogs object here.

05:57 And so memoization in a React context is actually kind of similar. You can genericize this a little bit and we have this memoize function that allows us to take a function and memoize that function. It's a pretty naive implementation of memoization,

06:15 but feel free to dive into that if you're kind of interested in what's going on there. So let's talk about React here for a second before we get into actually doing this exercise. So this useCallback function or hook is a special hook that will memoize the function that it's given.

06:33 So basically it stores the function that you give it the first render, the first time we run, stores it in a special place, and so long as the dependency array never changes, it's going to give you back the same one that you gave the first time.

06:49 So memory is being allocated every time this renders for this function. We're calling useCount again. It's going to render, whatever, and we have a function. We're going to pass that function to useCallback and useCallback is going to be like, okay, great, here's the new function you gave me. I have the old function you gave me over here.

07:07 Here's the dependency array. Oh, that's empty. There are no changes. So instead of using the one you just gave me, I'm going to throw that away. I'm going to give you the previous one that you gave me and that way it always stays the same every single render.

07:20 And so this increment will be the same every time and now passing that to useEffect is effectively going to do nothing. useEffect will not have to rerun because increment changed because it will be the same one that useCallback got the first time we rendered. So that's kind of interesting how all of that works,

07:39 but this is, again, one of the reasons why I tend to try and avoid memoizing or creating custom hooks unless they actually are of value. Because if we didn't do this, if we didn't make a custom hook,

07:53 then what we could do is get rid of this useCallback, get rid of that, and then we could actually just move increment into this and now we're all set. SetCount you could include, but React ensures that setCount is going to be consistent every single time.

08:11 And so including it in the dependency array is not going to harm anybody. Not including it also won't harm anybody because it's going to be the same setCount every time. But this is one of the reasons why I like to avoid the premature abstraction of moving things into a hook

08:25 because it does typically end up complicating things a little bit more when it comes to dependency arrays like this. OK, so let's look at useCallback a little deeper. So this isn't exactly how React implements useCallback, but this will kind of give you an idea.

08:42 So it accepts the callback, returns the callback, but it does more than that. It just keeps track of the last one and it'll check, did the dependencies change? If they did, then we'll assign the last callback to the new one that you gave us and return that one. Otherwise, we'll just return the last callback. That's the idea of what's going on.

09:00 Certainly not how it works at all or how it's actually implemented, but that hopefully clarifies how useCallback works for you. So useCallback is actually a helper built on top of another hook called useMemo.

09:18 useMemo allows us to memoize more than just callback functions, but like objects or symbols or whatever, any sort of thing. And so if we didn't have useCallback and we wanted to memoize a function, then what we'd have to write is something like this, which is why useCallback exists.

09:37 So it's basically useMemo accepts a callback that returns the thing that you want memoized. And so this is a callback that returns the thing we want memoized, which is a callback. And that's why we use useCallback. Okay, great.

09:50 So we're going to be making a custom hook for our blog filtering UI and we're going to be using some of these concepts in this exercise. I hope you're ready to get going because I've been talking a long time. So let's get into this first step of this exercise.