Image by Arūnas Naujokas from Unsplash
DESCRIPTION
Let's learn how to fetch data in React using fetch, axios, and react-query. We'll create a custom useApi hook and build a simple Pokemon application using pokeAPI.
Introduction
In this article, we'll build a Pokemon application using PokeAPI and Material UI GridList component. To do that, I'll guide you through how to fetch data in React with hooks.
Function components in React are great but data fetching with them can be cumbersome at first. When I moved to using hooks it took me some time to grasp what's going on. If you're starting out, check out Hooks introduction from the official documentation. Additionally, read about useState and useEffect hooks as we'll need them.
I'll start from using fetch to get the data from PokeAPI, then refactor the code for using a custom
useApi
hook with axios, and end up with react-query as it's an amazing library.In the end, you should have a fair understanding of hooks, data fetching in React and build the below Pokemon list application. 😎
Important: React devs are working on Suspense API and server-side components, both of these exciting features can impact the ideology behind data fetching - still, hooks are instrumental to understand all of these concepts and I highly encourage you to dive into this article.
Fetch PokeAPI data with hooks
Let's start from the mentioned
fetch
, we'll use it with async/await syntax. It's pretty much a syntatic sugar built on top of promises.Note: I'm using a codesandbox to develop this application but you could set up a local repository with e.g. CRA or any favorite boilerplate you want.
Fetching data is not so straightforward
First, we're setting the initial
pokemons
state variable to []
(empty array) using the useState
hook. Then, using useEffect
hook and fetch
with async/await
(please, notice the usage of .json()
to get an actual JSON from HTTP response), we get the data from pokeAPI, limiting it to the first 100 Pokemons. After that, we save data.results
using setPokemons
function. In the end, we return a simple JSX
to render the list of Pokemon names.However, we end up in an infinity loop.
useEffect
hook runs when the component mounts and when it updates. We're setting the data of Pokemons after we receive it from the fetch
which updates the state and triggers useEffect
to run again.To prevent that, we need to add an empty list of dependencies to
useEffect
hook, then it'll run only once - when the component mounts. And that's what we need.No more infinity loop. 🎉 But still, when I run the above code in the sandbox environment, we get this warning: "Effect callbacks are synchronous to prevent race conditions. Put the async function inside".
async/await
returns a promise but useEffect
should return either nothing or a cleanup function. Let's fix that.Thanks to our
fetchData
function inside of the useEffect
hook, we got rid of the warning. 🎉If you'd like to get the data when a user types something in the search field (e.g. to find a particular Pokemon), you can pass additional dependencies to the
[]
dependency list on useEffect
. This will be a bit problematic as each time the user types in, useEffect
will fetch the data. You'll need to use some debounce technique to solve it.If you need to get the data on button click, create an async. function and assign it to the
onClick
event.The above solutions are out of the scope of this guide. I covered them in a separate article as there are not too many "out of the box" resources to explain these topics.
What about error handling and loading state?
In a real-world project, you can't let the application crash when something goes wrong. There should be a gently alert to the user which is often called a fallback UI. We won't use React ErrorBoundary for this project for simplicity reasons but instead, we'll handle the error inside of the component directly using the Alert component.
For loading, we'll show a spinner component when data is still being fetched using the CircularProgress and Grid components.
We've added state responsible for error and loading, introduced try...catch to make sure we catch all the possible states. When the error happens, we'll show the
Alert
component with an error message.Loading is a bit more tricky as we set a default value to
true
. It was intended as we don't want to render an empty page when there is no data yet. Instead, CircularProgress
will show up.Everything is almost perfect but we're mixing logic with rendering in one component. You're guessing right, it's time to create our custom
useApi
hook component and refactor the code a little bit.useApi hook with axios
Let's have a bit of fresh air and switch to axios for data fetching. It's a nice library similar to
fetch
but has less boilerplate, better error handling, and can monitor data loading (totally not needed for our case 😅).I've written a Knowledge Pill for fetching data from an API using
fetch
via a custom hook. The above code snippet is almost identical with the exception that it uses axios.get
(less boilerplate, no check for .ok
) instead and is named useApi
and not useFetch
like in the pill. For purposes of our Pokemon application, we don't need to use any other methods than the .get
method from axios
.To learn more about custom
useApi
hook I highly recommend going through the above pill and comparing both solutions (please, play a little bit in the codesandbox with pokeAPI too).It looks clearer when all the logic is extracted in our
useApi
hook. As you can see on the highlighted code, useApi
returns error
, isLoading
, and data
. We need to extract results
from data
, rename it for declarativity purposes to pokemons
and render as before.What if there already exists a hook for data fetching? 🙈
Unfortunately (for me), I'm not a precursor of custom hooks for data fetching. We mentioned
react-query
in the introduction. Let's see how that can simplify our life. (I'll still leave useApi
hook in the code sandbox for reference).I skipped the setup of the
QueryClientProvider
from the react-query
and focused on useQuery
. You can read documentation to configure it in your project or take a look in the codesandbox (index.js).useQuery
hook has a very similar structure to our custom hook with small exceptions. The first parameter is the query name, in our case "pokemons"
string argument. The second parameter is a function to execute the query, it could be .get
method from axios
but we used fetch
in getPokemons
function to showcase useQuery
hook works with it.It appears, we didn't really need to write any of the data fetching code and just use
react-query
. Although, I hope this data fetching journey opened your eyes, and either it's an abstraction or not, you know what you're doing when writing asynchronous code in React. 🙇♂️As we spent quite some time on the data, let's make it pretty and shiny with the
GridList
component.Using GridList and fetching Pokemon image
Going from the top, I added a couple of
GridList
related components for styling purposes. Then, I've implemented getPokemon
function to receive the Pokemon data based on provided url
. The function was used inside of a newly created PokemonTile
component with a combination of react-query
(please, notice anonymous function usage to pass an additional url
argument).After getting Pokemon data, I was able to render the
PokemonTile
to present it on the screen. Outer App
component renders a list of PokemonTile
components based on the pokemons
query (limited to 100).REST API, GraphQL, gRPC?
All our API calls were based on REST API so far but GraphQL is a great alternative. There is also a gRPC buzzword but I didn't work with it at all.
GraphQL is great as it has a single endpoint to rule them all, the client (you, browser) can decide how much data should be fetched, everything happens through queries and mutations. Additionally, it has an auto-generated schema feature (helps a lot to understand API structure, similar to e.g. Swagger, just built-in). On top of that, there is e.g. apollo-client library for React which supports cache management (you know, cache invalidation and naming things... 🙈) and has amazing hooks support out of the box.
Not going into the details. However, I've written another article to present simple usage of GraphQL query through pokeAPI with browser fetch. Interested? Check it out!
Summary
The Frontend journey never ends. To become a top-quality React Developer you need to get a good hang of data fetching. This was my first big series about explaining crucial topics based on project building, hopefully, you had some fun and finished the project with me along the way. 😊
... but if not, let's take a look at the preview of it one more time to avoid scrolling all the way top.
As I already mentioned in the related pill, time for you to turn this project into the best Pokedex online ever created! 🔥
Big thanks for reading the article, you're awesome! 🙇♂️
You can also find me on:
Thanks for all the support. ❤️