Complete Intro to React, v6 Custom Hooks
This course has been updated! We now recommend you take the Complete Intro to React, v8 course.
Transcript from the "Custom Hooks" Lesson
>> So we just did hooks, those are super fun. And I'm gonna say 99% of your use case is going to be using just React hooks directly inside of your components. But there is gonna be rare occasions that you discover some common piece of functionality in your application.
[00:00:19] That you're just gonna decide, I wish I could reuse this hook over and over again, this kind of higher level more abstract hook rather than just using the same kind of patterns over and over again. So I'm gonna teach you how to create your own hook. So if we go back to our application.
[00:00:40] We have this animal drop down. So if I select dog, I want it to be populated with like Husky, and Havanese, and Vishal Frieza, and Poodle, and stuff like that, right? And let's imagine I have to do that several places throughout my application, and I don't wanna reimplement and replicate that logic over and over again.
[00:00:59] It'd be nice if I just had a hook that is like, if I gave it an animal, it gave me back a list of breeds. So we're gonna go do that right now. What I'm gonna tell you right now is I'm showing you right now how to create a custom look just to show you how to do it.
[00:01:13] I would not necessarily do that in this particular case, right? If you have a place where you don't have to rip out logic and make it more modular, and things like that, you don't necessarily need to do that. In this particular case, I would just leave all that logic here in the search params component because that's where it lives, that's the only place that uses it.
[00:01:34] But for now, we're gonna abstract this into a custom hook. So create a new file here, and we're gonna put this inside of the source directory and it's gonna be called useBreedList.js. This is gonna be our custom hook. So, the thing about hooks is they use other hooks.
[00:02:04] So we're gonna say import [useState, useEffect], because we're gonna use both of those from React. We're gonna make a local cache, which is just gonna be an empty object. The idea here being if I select cat and I get like tabby and Siamese and then I select dog and get like Husky and be Vishal Frieza and then I go back to cat.
[00:02:33] That breed for cats us not gonna have changed. I don't need to re-request it, I'm just gonna store it locally, okay? I'm gonna export a default function called useBreedList, That takes in an animal and it's gonna give back a breedList and it's a hook. So this is gonna read a lot like a React component, const [breedList, setBreedList] = useState.
[00:03:09] Something I'll say right now, you'll notice that I always call this set block, that's kind of a personal habit. I'm not gonna say like everyone does it that way, but you'll see it's fairly common to call these breedLists and setBreedList or x and set x, right? It's just kind of nice to know that if I see setBreedList is like okay, this is referring to the breedList.
[00:03:28] Okay, and then we're gonna give it an empty array because by default it's gonna be an empty array. And then we're also gonna have a status here. Status and setStatus = useState. And this is gonna be what I would call an enumerated type. So basically, the programmer that's consuming this because now we're creating code that we intend that other developers will use, right?
[00:03:58] So if no one's ever called it before, it's gonna be unloaded. If new stuff is loading, we're gonna say it's loading. And it's loaded stuff, we're gonna say it's loaded. So it's just a string that represents what the state of the hook is in. We don't really use it anywhere, in fact, we could probably leave this out.
[00:04:17] But this is what I would do personally if I was creating this, so I'm gonna make you do it with me. Okay, we're gonna say useEffect, Take in a function here. And the first thing I wanna say, if animal, so if no animal's provided, then setBreedList to empty, right?
[00:04:49] Because if you have no animal then you have no breedList, that makes sense. Else if, if I already have a copy of it in my local cache then just serve that immediately, setBreedList localCache:(animal), Else requestBreedList. Okay, so so far, This is kind of the logic that we're gonna use in the effect to say, Right, okay.
[00:05:38] No animal, return an emptyList. If I have something locally, I've already requested this in a previous time, then serve that, otherwise go out to the API and go get it. Outside of the effect rather, we're gonna make a function called async function requestBreedList. So the first thing we're gonna do is set breedList to be empty.
[00:06:14] We don't wanna have this weird interim state where like I'm on dog, I have Husky and Poodle and then I change it to cat. Immediately I would expect that breedList to be empty while we are requesting new data, right? Because you wouldn't wanna have this weird interim state where I have cats that are Huskies cuz that doesn't make any sense.
[00:06:33] We're gonna set our loading status, so set status to be loading. Then underneath that we're gonna say const res = await fetch. I'm gonna go to http://pets-v2.dev-apis.com/breeds animal = $animal, like that. So http, not secure, which is fine for our demo application, pets-v2.dev-apis dev-apis.com/breeds?animal = our animal that we got back from the user.
[00:07:39] Const json = await res.json and now json is gonna be whatever we got back from the API. So local cache is gonna be equal to, json.breeds. That's gonna be what we got back from the API, I'm sorry, local cache animal. Right, because we wanna save that results so that in the future if they come back to it that they're gonna get that response over again.
[00:08:14] If for whatever reason the API was down or something like that, we don't want our app to crash, so you can say or empty array. So that worst case scenario that they're just gonna get back an empty array. I guess you'd have a cache problem at this point but it's better than crashing probably.
[00:08:34] SetBreedList to be json.breeds or even better, localCache[animal], so whatever we just stored in local cache animal. You could also just put this if you want to, but this is kind of self referential, so that's good. And then we're gonna set a status at this point to be totally loaded.
[00:09:03] I guess in my notes had this inside of the effect, so let's just go ahead and do that. I think it'd work either way, but let's just put it in there. So I just moved this inside of the effect, right? So use they effect here, this now is async function is inside of it.
[00:09:22] I remember why I did this, because it makes it play nice with JSLint. And again another VS code fun tip or trick for you, I just highlighted this code, I hold Alt or Option and just up and down and you can move it line by line up and down, okay?
[00:09:36] So when do we want this effect to run again? That's the question we need to ask yourself right now. When does this effect run? Well, honestly, it'll tell you that basically it's what animal is. Because animal is the thing that we reference from outside of it and that's what we need it to rerun on.
[00:09:54] So every time that we get a new animal back from the user. That's when we want it to run. There you go, so now we can kind of have this guarantee that any time animal changes, we're gonna get a new breedList back from the API. So what we wanna do is return here at the bottom is we're going to return the breedList and the status.
[00:10:22] And we're following this convention, you don't have to do it this way. If you feel more comfortable returning an object of like breedList and status, nothing's forcing you to do it the array way. It's just people in React tend to write code that way so I tend to follow that.
[00:10:37] But it's totally up to you how you wanna return that. But that's we're gonna do for right now. All right, so walk back a second with me. We're gonna use this breedList inside of SearchParams. Imagine that you wrote this and then your colleague is going to use the custom hook that you implemented.
[00:11:04] This has hooks inside of it but as long as they're not doing any sort of conditionals are for loops inside, they're not doing bad things with hooks, this is still gonna call all these hooks in the exact same order, right? That's how this custom hook logic works is that as long as all the hooks are called in the correct order, the same order over and over again, it's just gonna work.
[00:11:25] And so there's no magic here, that's why you don't have to have a special hook wrapper or anything like that. It's really just another function that's calling other hooks. There's not really that much special about a custom hook, is what I'm trying to say. Okay, so let's head back over to SearchParams now.
[00:11:42] We're going to at the top, import useBreedList from ./useBreedList. And then here for breeds, we are gonna say const [breedList] = useBreedList with animal. So this goes back to, The question that we were asked earlier is what happens if I have dependent state and you can do it this way.
[00:12:21] So you might remember that we also had status there, but we're never gonna use it, so I'm just gonna ignore it. It's there, I'm just not gonna read from it, so I'm not going to use it. Now I have breedList or you can call this breeds if you want to, cuz that's actually what we call it lower in the code.
[00:12:40] So I'm just gonna call this breeds from useBreedList. And now if I scroll down here, breeds here, it's already reading from the empty array that I had before, so this should just work. So if I save this and head back over to my application, no breed, makes sense.
[00:13:03] We don't have any animals selected. If I go down here and say dog, now look at it, there's a bunch of dogs in here. And if I go to cat, get a bunch of cats, so on and so forth. And we can actually watch this happen in our network tab.
[00:13:27] So let's look at, I'm gonna clear this, and I wanna look at just XHR. All right, so you can see here I have the pet request that's what you would expect here. That's what got all these pets in the first place. And if I switch this to dog, notice it makes a request of breeds, animal equals dog, rabbit, breeds rabbit.
[00:13:54] Now what happens if I go back to dog? It doesn't make another request, right? Because we had that local cache of it, so, we're not going back to the API. We're just using the one that we all ready requested before and that's why it still works even without requesting from the API again.
[00:14:11] So this is kinda the magic of hooks, as long as everything has its correct data dependencies, that kind of cascading requests all just happen automatically. The machinery just works as designed. So again, you're not gonna create too many custom hooks. You shouldn't, in this particular case, we kinda overkilled this.
[00:14:32] There's a decent chance that this should just live all in SearchParams. But at the same time this component's getting kinda long, and so it is nice to break out pieces. So I mean, use at your discretion. But one thing that you have to keep in mind is this useBreedList becomes a black box to your future self.
[00:14:52] And so it can be a source of bugs because not all of the logic is coexisting with each other, so it's a balance, right? The thing that I want to invite you to do is just choose readability. Choose readability, choose maintainability. Do the thing that you think is gonna make this code not the most clever or the cleanest but the thing that's just easiest to read for you in the future.