All right. So we've put together all of this logic right here inside the useEffect hook to update all of these state properties to output the data or a loading message or an error if there is one, and everything is working fine.
And it's fine to be left like this, but what if we want to do the same kind of thing in another component in the future where we fetch some data, we create state for the data itself, the error, and isPending property etc. We'd essentially have to rewrite all of this code in that component again, and that's not very efficient.
It's not easy to manage especially if you're using this code in a few different places in your application. So it would be good if we could make use of all of this code again in different components, so make it a bit more reusable so we don't have to continually write it out again and again.
For example, what we could do is we could rip it all out of this component and put it in its own JavaScript file and then we just import that into this file to use it, and we could also import that into any other component that might need to use the same logic in the future. And this way we're only writing and maintaining the code in one place and not in several different places over different components that need to fetch data.
So when we do something like this by externalizing the logic into its own file, we're creating something called a custom hook in React. So a bit like useState and useEffect have their own specific functionality as hooks, we'd be creating a custom hook with a specific ability to fetch data.
Creating the useFetch hook file and moving the logic
In order to make this custom hook the first thing I'm going to do is make a new file inside the src folder and I'm going to call this useFetch.js.
So inside this file we just need to place all of the functionality to fetch the data exactly as we did over here inside the Home component. But first of all we need to create a function to put the code in, and this function will be the hook itself. So I'm going to say const and I'm going to call this useFetch, the same as the file name, and set it equal to an arrow function.
Now custom hooks in React need to start with this word use otherwise it won't work, so use something or other. In this case useFetch.
So now what I'm going to do is just copy and paste all of this logic right here. I'm going to grab that and come over here. And in fact we need the useEffect as well, so let me undo that and grab the whole thing right here including the useEffect and we're going to paste it inside this hook right here.
Now we do need to make some tweaks. First of all, we need to import useEffect and also we need to register all of the states as well because currently the state is over here but we're not setting that state here anymore; we're setting that state down here. So let me also grab all of this first and paste it inside this function at the top.
So now we're updating all of these state properties. And also what I need to do is import useState and useEffect inside this file in order to use them. So let's do that as well: import useState and also useEffect and that is from React.
Okay. So there's a couple more things I want to do. First of all at the end of the whole file we need to export this function, so we can say export default and then it's useFetch, and that's so we can use this in other files in the future and other components.
Refactoring names, return values, and URL parameter
Let's get that right. Okay so that's the first thing. The next thing I want to do is change this from blocks to be data because I want to make this reusable. So in the future if I'm using this hook to fetch data it might be to fetch a different type of resource for example tags or categories, so it makes no sense to call it blogs all of the time.
So let me change it right there and also I want to change it down here where we try to set the blocks. It's now set data and I think that's pretty much it. Yep.
And by the way it doesn't matter that we call this parameter data even though this is called data because this is a local version inside this function so it doesn't matter that the names clash.
All right so I think the last thing I need to do is return some values from this function. So you know that when we call some kind of hook over here we have the useState; it returns some values, right? So we can do the same thing in our custom hooks.
So at the bottom of the useFetch function I'm going to return some values. Now what I'm going to do is return an object and place three values inside this object. Now the return value can be whatever you want by the way. It could be an array like useState's or it could be a string or a boolean. In our case it's an object with three properties: data, isPending and error, because when we use this useFetch hook inside another file in the future we want to be able to grab those properties from the hook. That's what we want to get.
So let's return first of all the data, then isPending, and also the error. So these three things.
Okay so there's one more thing I want to do and that is to pass in the endpoint or the URL into the function rather than hard code it here because again if we're using this useFetch hook in another file or a different component at some point it might not always be to the same endpoint so we don't want it to be hard coded right here.
So I'm going to cut that and instead we'll pass in a URL as a parameter and that will be what we fetch. So this is going to be the endpoint and we need to pass it into useFetch whenever we call it in the future.
Now that also means that we're going to place the URL as a dependency to useEffect and that means that whenever the URL changes it's going to re-run this function to get the data for that endpoint. So that's pretty much this file done.
Importing and using the hook in components, naming flexibility, and wrap-up
All we need to do now is import this useFetch function inside our Home component and then use it. So let's do that by saying const and we want to destructure the three properties we get back returned: data, isPending and error. So I'm going to say data, isPending and error and by the way we could have used an array much like we get an array of values back from useState; these could have been in an array instead.
But typically I like to use objects because then the order of these properties doesn't matter when we're destructuring them and we can just grab maybe isPending without getting the others if that's all we want.
Anyway let's set that equal to useFetch. Click on this and it should auto import it at the top and then remember as a parameter we need to pass through this endpoint so that's the resource we're trying to fetch.
So fingers crossed this should all work because now what we're doing is grabbing these three things back and then we're still outputting the data once we get it whether that be an error, isPending or the blogs. And by the way this is now called data but we're still trying to pass the blogs in.
So what we could do is pass in the data here instead and that would work or what we could do is name the data blocks inside this component by adding a colon and then whatever we want to name it. So we can call it blogs and that means grab the data but call it blogs in this context.
So now when we pass blogs in we're really just passing in the data. All right so let's save this and see if it works. Yep everything works. Cool.
So there we go my friends that's how we can make a custom hook and hopefully you can see how this is much more reusable now. We don't have to redo all of this logic in every component that needs to make a fetch; all we need to do is one line of code. We pass the endpoint in; it doesn't matter what it is, it's going to try and fetch that data and bring it back to us.
We get a loading state and also an error if there is one which we can use in that component and it also means our components look a lot neater and tidier as well and they're much easier to read.