Creating a Custom useFetch Hook
Okay then, my friends. So we have Strapi running in the background and we have all of this review data that we want to grab from the front end, from our React application over here, so that we can show it in the browser. So how are we going to do this?
To begin with, we're just going to use that REST API that we saw a few tutorials back when we used Postman. So we're going to create a hook, a custom hook which is going to be used to fetch some data from a REST API. But later on, we're going to be using GraphQL. I don't want to overcomplicate things for now, so we're going to stick with that REST API to begin with.
Okay then. So let us start by creating a new folder inside source, and I'm going to call this hooks. So we'll place any custom hooks inside this folder. Now, by the way, you don't have to do it this way. If you don't want to create a hook, you can just fetch the data directly from the home page component if you wish to. However, making it a hook is better because it becomes more reusable, the logic, and we can use it later on in other components as well. So let's create a new file, and I'm going to call this hook useFetch.js.
Defining State and Structure in useFetch
Okay. So inside here, I'm going to first of all import a couple of things that we're going to need. We need the useEffect hook and also the useState hook from React.
Okay, so next up, I want to create a function. So const and we'll call it useFetch and we set it equal to a function. This is going to be the function that actually does the fetching. And at the bottom, we need to export this. So export default useFetch. All right then.
Now, this hook is going to take in a URI, so basically whatever we're trying to get data from. So the endpoint, if you like. And inside here, we need a few pieces of state. So what I'm going to do instead of typing those out is just paste them in. So we use the useState hook three times. And we use the first one for the data that we're eventually going to get back from the fetch request. So we're going to initialize this to be null, but later on we'll use the setData function to update that to whatever data we get back from Strapi. Then we have one for the error, if there is an error. Again, to begin with, it's null, but if we get an error back from Strapi, we'll use this function to update it. And then finally, we have loading and setLoading, which is true to begin with. So whenever we start to use the hook useFetch, it's going to initialize the loading state to be true, and then once we've finished fetching the data, we'll make this false.
All right. So we also need to return those values at the end of the hook as well. So let's paste that in: return loading, error, and data.
Implementing Async Fetch Logic with useEffect
Okay then, so the next thing we need is the useEffect hook right here, because this is going to fire when the component renders, whatever component we're using this hook in. So this useEffect hook is then going to fire a function when that happens, when the component renders, and inside here is where we're going to fetch the data.
So I'm going to create another function inside this called fetchData, and the reason I'm doing that is so we can make it async and we can use the await keyword inside it. So it's equal to an async function like so. And that's because we can't make this async right here. We can only create a function inside it which is async. And then at the bottom of useEffect, we just need to invoke the fetchData function so it runs.
Okay, so inside here, what do we want to do? Well, first of all, we're going to setLoading to be true, just in case for whatever reason this is the second time it's run, and this is set to false for whatever reason. We're resetting it equal to true when we try to set the data, or when we try to fetch the data, rather. Now after that, we're going to try to do something, and if there's an error, we're going to catch that error. So let's pass it in like so. And spell catch correctly. So what's the thing that we want to try? Well, we want to use the fetch API to fetch the data using this URI, this endpoint.
So let me just paste in these two lines of code. We say const response = await fetch(), so the fetch API, and we pass in the URL. In fact, let's call this url up here instead of uri. And then down here, we say const json = await response.json(). So this is how we actually get the data and pass it into a JavaScript object, the data we get back from the Fetch API.
Okay, so now we have that json, all we need to do is update the data to whatever this is equal to, because this is going to be equal to the object that we get back, the reviews. So let's do that. I'm going to say setData to be the json, and then also I want to setLoading at that point to be false because we've finished the fetch and we have the data. We can set this to be false now, so that in our template, when it's loading, if this is true, we can say, "Look, we're loading the data." As soon as it's false, we can take away that loading message.
All right then. So if there's an error with the fetch, all we need to do is update a couple of things. First of all, the error with whatever error we catch right here, and then update the loading property to be false, because even though we get an error, we're still no longer trying to fetch anything. We just get an error back, so the loading is false. Okay then. So finally, as a second argument inside the useEffect, we need to pass in our dependencies, which in our case is just going to be url. So if this ever changes for whatever reason, then this will run again.
Awesome. So I think that is pretty much it for our useFetch hook. Now all we need to do is use it in our HomePage.
Integrating the useFetch Hook in a Component
So then the first step is to import that hook at the top, so useFetch from hooks/useFetch. And then inside this component, we can use it. So I'm going to say const and we're going to destructure those three things from this hook, which is these right here. So I'll copy those and paste them in right here. So we'll destructure each of those from useFetch like so. And then we need to pass in, if we take a look, the URL of the data we want to grab. So the endpoint. So I'm going to pass that in as a string: http://localhost:1337/reviews to grab all of the reviews.
Okay, awesome. That's all we need to do. And that hook is going to go out, fetch the data, and it's going to update these values at different points during that fetch. So to begin with, loading is going to be true, remember? Now I want to show a little loading message instead of the actual content when this is loading, when loading is true. So in order to do that, I'm going to do an if check: if (loading) like so. And I'll just return a bit of template, a paragraph tag saying "Loading". Now you can make a better loading message or a spinner if you want. That's all I'm going to do for now.
Now, if there's an error, I want to do something similar. So I'm going to copy this line and paste it down here. So if (error), then we'll return some other message and we'll say, "Error," like so. Again, you can make a better error message.
Rendering Dynamic Data with React
But now, when we first load this, loading is true to begin with. Remember, inside useFetch, we set it to be true to begin with and only set it to false after the fetch is done. So it's going to be true to begin with. So it's going to come down here, this will be true, and it will return this template. And because we return something, it doesn't go any further in the code. So we don't see this template or this template. If it's false, it carries on. If there's an error, we return this and it never renders this. If it does not, then it carries on and it returns the template.
So it's only going to return the template basically when we have data. Now what we want to do inside this template is map through the data and output a bit of template for each review. So to map through something, we do curly braces and then say data.map() and then we find a function for each item in that data array. And for each item, we get access to that individual item, which we'll call review, and we're going to return a bit of template for each one.
So the template is going to be a div first of all, and then this div is going to have a key property, because React needs the parent element inside the map right here to have a key property so it can keep track of all the different elements. And that key is going to be equal to a dynamic value, so curly braces, review.id, because remember we have an ID property on each review. If we take a look at the data, it's this thing right here: three, one, five, or four. So that's the key.
I'm also going to give this a className so we can style it shortly. className is going to be equal to review-card. And let's close that div off. All right then. So inside that div, I want to output several things. First of all, the rating. So let's do another div, and that is going to have a className equal to rating again, so we can style it later. And then right here, we're going to output some dynamic content, so curly braces, review.rating. All right then.
And then below that, we'll do an h2, and that h2, inside, is going to have the review title, so review.title. And then I'm going to do, oops, close your curly braces, I'm going to do a small tag right here and later on I'm going to list the consoles or the categories that this review is in. That's when we come to work with relational data, so this is just a placeholder for now.
After that, I'm going to do a paragraph for the review body, so review.body, like so. And then after that, we'll do a link to read more about this. Now this link is going to go to the details page. So first of all, we need to import the Link at the top from react-router-dom. So let's do that at the top: import Link from 'react-router-dom'. And then we'll create the link at the bottom. We need to say where this is going to. So to is equal to, and it's going to be something dynamic. We'll use a template string so we can output a variable. And it's going to go to /details/, and to output the variable, it's dollar sign, curly braces. This is how we output a variable inside a template string. And inside the curly braces is going to be the variable itself, review.id. So the ID of whatever review is currently being cycled through. And right here, we'll say "Read more."
All right then. So let's save this now.
Refining the UI and Inspecting Data
And give it a whirl. Take a look at the browser. And now we can see this is the review, or rather the rating, the title, the console list eventually is going to go here, and then the body. And we get the same for each one of these. And at the bottom of each one, we get the 'Read more'. If I click on this, you can see we go to the details page for that review. We don't see anything yet, but we'll tackle that later. But this is now all working.
Now, on the homepage, I don't want to show the whole body. I just want to show a little snippet of the body. So let's come to review.body and I'm going to say .substring(), and then we're going to go from character 0 to 200. So that gets us basically the first 200 characters of the body, and that's all we're going to show. And then after that, we'll do ... outside the curly braces to show that there's more content.
Okay, so that looks a bit better. All right, cool. Now, one more thing. I just want to log the data to the console here. So console.log(data), which is this thing right here. And if I save this, and by the way, if I refresh, you'll notice the loading message flicker very, very quickly. There you go, you saw that because that was the time it took for it to load, and that's when we show this template right here.
Okay, if we now try to do something like reviews with a double s and save, we can see the error. So let's have a look at that data in the console. I'm going to save that again and open up the console, and I'm just going to refresh to get rid of that error. And now we can see this is the data we get back. So from that fetch right here to this endpoint, we're getting back this data, an array of these things. And in each review, we have all of these different properties: the body, when it was created, the ID, when it was published, the rating, title, and when it was last updated. So that's all we're doing. We're cycling through this data right here using the map method, and for each item in that data, which we call review, we output a little bit of template.
All right then. So there's
Applying CSS for a Polished Look
one more thing I want to do just to make this look a little bit better, and that's just to add some CSS inside index.css. So underneath the rest of the stuff, I'm going to paste this in, and again, you can get this from my repo.
So we have this selector for the review-card, which is every review that we list out here. This is a review card, this is a review card, etc. And we take that, we give it a background of white, a bit of margin, a bit of padding and position: relative. Then the rating inside each of those review cards, that's this number right here, we style as well. position: absolute, and it's going to be relative to this because we positioned this relative. top: -20, so it goes above the top of the card a little bit. left: -20, so it goes left of the card a little bit. Background is that purple color, font size 3em, width and height of 90 pixels, so it's a square. Text-align center, and color white. The h2 in the review card, which is the title, we take away the margin. And then the small tag, which is where we're going to list out the categories or consoles later, we give a margin right and a color of gray.
So if I save this now, hopefully this is going to look a bit better. And it does. So there we go. Now we've fetched data using the Strapi REST API and a custom hook from our React application.