Check out a free preview of the full Complete Intro to React, v8 course
The "useBreedList Custom Hook" Lesson is part of the full, Complete Intro to React, v8 course featured in this preview video. Here's what you'd learn in this lesson:
Brian live codes a custom useBreedList hook that determines which animal has been selected and provides a list of breeds based on that animal type. Student questions regarding why requestBreedList is inside useEffect and why status is being used if it is being ignored in SearchParams are also covered in this segment.
Transcript from the "useBreedList Custom Hook" Lesson
[00:00:02]
>> We're up to 05 useEffect. So if you're behind or wanna just like copy that, that's where we are. Anything else to say about that? I don't think so. Cool, yes, this is called a controlled form. So we're using React to precisely control this form. This is actually not best practice.
[00:00:28]
Typically what you'd really wanna do because we're keeping track of location and breed and animal, we actually don't need to. The only time that we ever really use these bits of information is on submit, right? If you're familiar with how forms work, you can just grab things off of the const formData, =new Form data blah, blah, blah, you can pull it off of the form, right?
[00:01:02]
That's called an uncontrolled form. And then you don't have to worry about unchanged events and stuff like that, you just let the browser handle all of that. We'll talk about this in a little bit. But I wanted to show you first how to do a controlled form cuz it's a really succinct way of understanding how state works.
[00:01:20]
But this is not the best way of doing forms, I'll show you that here in a little bit. Okay, So we have animal. When I select bird, I wanna have a dropdown of the correct breeds, right? So that I see parrot, African gray, macaw, things like that. So we're gonna build a custom hook to do that for us.
[00:01:48]
We could totally just put it in another useEffect here and just do that directly from within SearchParams. But I wanna show you how to clean this up and make it really nice, and make a little reusable hook for us. So that we can say, instead of useEffect request from API,.blah, blah, blah, what we can actually do is say, useBreedList, and we give it the animal and we get back a list of breeds.
[00:02:14]
It's gonna be really nice. So I want you to create a new file here in your source directory and call it useBreedList. And here, there's gonna be node.jsx in it, so you can do .js, or you can do .jsx, doesn't matter. I'm gonna do .js. I have no reason for that.
[00:02:36]
If you wanna be consistent, do jsx. If you wanna say that there's no jsx in here, therefore I'm not gonna put jsx, do that. I chose .js because that was what occurred to my brain two weeks ago, so that's what I did. Okay, import{useState, useEffect} from 'react'. Okay, I'm gonna have some localCache=blah, empty object.
[00:03:14]
And I'm gonna export default function, useBreedList that takes in an animal. So what this is gonna do, is it's gonna take in an animal. If it's seen the animal before, so if I do dog and then I switch to cat and then I go back to dog, it's gonna serve it from whatever from cache, right?
[00:03:41]
So it's gonna do, all right, I got poodle and Jack Russell Terrier back, then I switched to cat and then it came back. Chances are the breeds, they haven't invented a new breed in the past 13 seconds, right? So we can probably just give them back the same list, right?
[00:03:57]
Therefore we're doing this localCache maneuver. So I'm gonna say const [breedList, setBreedList], =useState of an empty array Const [status setStatus]. This is so you can give back, I'm unloaded, loading, loaded, right, so that someone could show different loading states based on that. And the useState, and then by default, it's gonna be unloaded.
[00:04:37]
Okay, we're gonna do a useEffect. So if no animal, setBreedList to be empty array, right? If they pass me an empty string here or null or undefined, I have no breedList, just send it to be an empty list. Else if localCache, if I've seen it before in my localCache, then setBreedList to be whatever is in localCache.
[00:05:19]
Okay, else requestBreedList. Okay, here inside of the useEffect, very essential here, be inside of the useEffect, async function requestBreedList. So we're gonna write the function that we call here. setBreedList to be empty, And setStatus to be loading, okay? From here, const res=await fetch, http:// or s, I think it works on both SSL and not, pets-v2.dev-apis.com/breeds.
[00:06:27]
And then animal={animal}. So we're gonna request from this API that Frondend Masters runs, a list of breeds, const json=await res.json Okay, localCache equals [animals], so we're gonna save it. If we call requestBreedList, this means that we didn't have it before, so we wanna save it, =json.breeds. If that doesn't exist, then just set it to be empty array.
[00:07:16]
SetBreedList to be (localCache[animal]) And then setStatus to be loaded. Okay, so now we have this huge effect. When does this effect rerun? When what changes? When animal changes. Every time that the animal goes from dog to cat, we wanna request the new breedList again, yeah? So, Run this whenever animal changes.
[00:08:01]
And then at the end, you're gonna return, breedList and status, using the same weird tuple, tuple, Methodology. It's a Python word if anyone's coming from a Python language, or computer science, but it's dumb. Apparently the correct way to say it is tuple, but that sounds dumb, so I say tupple, which is incorrect but sounds better.
[00:08:31]
Okay, so here we are. I told you that custom hooks are basically just other hooks packaged together, right? So we called useState and useEffect a couple of times in here. And then we're packaging this up so that now later, all I have to do is say useBreedList instead of blah, right?
[00:08:51]
So I took a really ugly bit of code and I encapsulated it. This is now testable, reusable, and now I can have this really nice black box, I feed an animal and I get back out breedLists. And now, I can go back into SearchParams.jsx. And I can say, import useBreedList from "./useBreedList".
[00:09:21]
And instead of having breeds here, I can just say const [breeds]=useBreedList (animal). And voila, I will always have a correct breedList now. Let's just go make sure that I'm not making stuff up, which I am one to do. Bird, I get all these nice looking birds back, I change this to be cat, I get cats back.
[00:09:59]
And if I go back to bird, Let's just look at our network tab, just to make sure that our caching is working correctly. Trash that, all right, so now I have animal. If I go to dog, I expect it to make a request, right? It does, right there, you see it.
[00:10:20]
If I go back to cat, Okay, makes one. If I go back to dog, I expect it to not make a request, right? It didn't. And yet I see all the dog breeds here, Cool, Pretty slick, right? It's kind of a nice way of encapsulating. There are people that will tell you that they actually never do effects directly in any of their components.
[00:10:51]
They always make a custom hook for all of the effects that they have. It's not the worst idea I've heard. I don't do it because I am lazy. And sometimes if I have a one-off effect, then by all means I'll just stick it directly in the component. If I use effect twice or three times, custom hook is a really good idea for that, or something like this.
[00:11:11]
I'm only gonna useBreedList in this particular application, but it was nice to get a lot of code out of this one, cuz how long would SearchParams be if I had all that in there? It's already a 90 line component, it's nice to not be 120 lines, right? So if I can encapsulate a lot of complexity into a custom hook, that's a good place to do it as well.
[00:11:33]
But what I'm trying to delineate here for you is, it's a personal choice of where you put things in a custom hooks versus when you don't. So there's extremes on both sides. As with most things, I try and be somewhere in the pragmatic middle. Yeah, Dustin, do you have a question?.
[00:11:52]
>> Why is requestBreedLst inside useEffect? And if the function inside of useEffect calls an async function, doesn't it have to be async?
>> Okay, let's answer them one by one. Why is requestBreedList inside of the useEffect? Well, what's nice about doing it this way, one, React encourages you to do it this way.
[00:12:21]
So if you move it outside like this, it's gonna say, hey, you haven't actually set that requestBreedList is in here. So then you say, requestBreedList, and this is like, okay, well, It makes dependencies on side of that, so therefore you should move it inside. So, one, React is just gonna encourage you to do it this way.
[00:12:41]
That's probably the primary reason, is so that ESLint stops yelling at me, which I do a lot of things to get ESLint to stop yelling at me, probably childhood trauma or something like that, I'm not really sure. But [LAUGH] the second reason is that this is all coming as part of one effect, right?
[00:12:59]
This is atomically what you want to happen after every single time this re-renders, and so you want this effect to happen that way. And so it makes sense to group all of these things together. So requestBreedList is all happening at the same time. It all has the same closure, it all has the same state.
[00:13:15]
It just makes it for a nice tidy individual effect. So that's the reason why we put it in there. You're asking for the async effect, why are we doing this? Doesn't this have to be an async function, which makes this an async? The answer to that is no.
[00:13:37]
So an async function, the only thing that's different externally from a normal function is whenever I call requestBreedList, because it's an async function, what does it return? A promise, every async function returns a promise. In fact, you can see this. You can see the type up there that it says, promise, right?
[00:13:58]
So if I said const promise = blah, this is now a promise that I could then await somewhere else, or I can do a .then here. But you can see here, I'm just ignoring whatever it returns, right? So because I'm not doing anything with return, I don't have to do anything about it.
[00:14:19]
Which is why I put this in an async function in the first place is because I wanted to do async await inside of here. And you cannot make effects async, because for reasons, we could get into it, it doesn't really matter. Suffice to say that the only thing you can return an effect is a non-async function.
[00:14:41]
Does it have to be non-async? No, it actually could be async. Anyway, you have to return a function. So if you wanna do async await inside of an effect, you have to make an async function inside of the effect, just how that works. Yep, that last sentence probably sums up better everything that I said before it, so just stick to that.
[00:15:02]
If you wanna do async await inside of an effect, you must create an async function inside of the effect. Cool, other questions? A question that someone might ask me is, why are we doing status if we're just ignoring it inside of SearchParams? It makes it really easy to test if you do it that way, right?
[00:15:30]
So later, in intermediate React when we go over the testing module, you basically wait for that to become loaded and then you can write tests, right? And by tracking that status throughout the entire thing, it allows you to write easy test for it. So just a suggestion, if you have a custom hook that is doing something that you have to wait on, I would recommend tracking a status, because then later when you go to do tests, it makes it very easy to test.
Learn Straight from the Experts Who Shape the Modern Web
- In-depth Courses
- Industry Leading Experts
- Learning Paths
- Live Interactive Workshops