How to fetch data from an API in react

Adel Benyahia
11 min readNov 19, 2022

In this tutorial we will learn how to fetch data from an external API using React.

We will use a free API service to simulate API requests: https://dummyjson.com/

  1. Using fetch
  2. Caching data
  3. Adding async / await to our fetch function
  4. Using custom react hook
  5. Showing the data in our UI
  6. Adding isLoading and erro states
  7. Adding Loading spinner animation
  8. The complete source code of this tutorial
  9. Using Axios
  10. React Query

Fetching API data using fetch

The Fetch API provides an interface for fetching resources (including across the network). It will seem familiar to anyone who has used XMLHttpRequest, but the new API provides a more powerful and flexible feature set.

fetch('https://dummyjson.com/products')
.then(response => response.json())
.then(result => console.log(result))

.catch(err => console.log(err))

In This code, we:

. Fetched data from the URL: ’https://dummyjson.com/products', using get method (default fetch method)

. Converted the response to json format

. Console.log the result

. If there is an error, console.log it

As you can see in the code above, fetch is an asynchronous function,’.then()’ blocks will be executed one after an other in order.

If an error occurred the ‘.catch()’ block will be executed

To implement that is react we will use the useEffect hook

  useEffect(() => {
const url = "https://dummyjson.com/products";
fetch(url)
.then((response) => response.json())
.then((result) => console.log(result))
.catch((err) => console.log(err));
}, []);

With the useEffect we will ensure that the fetch function is executed on every application run (component mount)

Adding the ‘,[]’ ensure that the useEffect will be executed one time

But what !!! if we execute the code and we take a look at the console

So? i will explain that

It’s due to React.StrictMode in your ‘Index.js’ file

StrictMode is a tool for highlighting potential problems in an application. Like Fragment, StrictMode does not render any visible UI. It activates additional checks and warnings for its descendants.

Strict mode checks are run in development mode only; they do not impact the production build.

To fix that we will add an AbortController to our function

The AbortController interface represents a controller object that allows you to abort one or more Web requests as and when desired.

We will associate our fetch function to this AbortController using signal:

{signal: controller.signal}

Then we will use the ‘..abort();’ method to cancel the fetch when we need it ( in the return of the useEffect clean up function)

    return () => {
controller?.abort();
};

That will cancel the fetch request if the component unmount.

Lets talk about another problem, if the fetch request is slow, and the component has already unmounted when the async fetch request finishes, you will see this error message from React in your console:

Don’t worry there is an easy fix for that

 useEffect(() => {
let isCancelled = false;

....

return () => {
isCancelled = true;
controller.abort();
}

We added a new variable isCancelled with a false value, then in our Clean up function we changed it’s value to true.

Now we will put the result of the data in a state, and before ‘fetch’ we clear the old data state

const [data,useData] = useState()
useEffect(() => {
setData(null);
....

The code will look like this

  const [data,useData] = useState()
useEffect(() => {
setData(null);
let isCancelled = false;
const url = "https://dummyjson.com/products";
const controller = new AbortController();
fetch(url,{signal: controller.signal})
.then((response) => response.json())
.then((result) => {
console.log(result);
setData(result);
})
.catch((err) => console.log(err));
return () => {
isCancelled = true;
controller.abort();}
}, []);

2. Caching data

The code is working well now, but if our back-end server or our internet goes down?

Our fetch request will fail for sure

For that we will add an offline capability to our application, and guest what its easy and with no extra libraries

We will you localStorage API:

What is local storage API?

The localStorage object provides access to a local storage for a particular Web Site. It allows you to store, read, add, modify, and delete data items for that domain. The data is stored with no expiration date, and will not be deleted when the browser is closed. The data will be available for days, weeks, and years.

. When the fetch succeed, we will add the new result to the local storage, with that we ensure that we always save to last fresh data.

. We will use the ‘url’ as the key of localStorage to ensure that we are using the right data

. Before saving the result to localStorage we have to convert it from json to string using JSON.stringify()

.We will use ‘setItem()’ to put the result to localStorage

.then((result) => {
setData(result);
const localResult = JSON.stringify(result);
window.localStorage.setItem(url, localResult);
console.log(result)
})

. If the fetch failed, we will check if we have a saved result in localStorage

. If ok , we get this data and show it to the user

. We will use ‘getItem()’

. And check if the data is not null we will convert it from string to json using JSON.parse()

. We update the data state

.catch((err) => {
const localResult = window.localStorage.getItem(url);
if (localResult !== null) {
const parseLocalResult = JSON.parse(localResult)
setData(parseLocalResult);
}
});

The code will look like this

const [data, setData] = useState();
useEffect(() => {
let isCancelled = false;
const url = "https://dummyjson.com/products";
const controller = new AbortController();
fetch(url, { signal: controller.signal })
.then((response) => response.json())
.then((result) => {
const localResult = JSON.stringify(result);
window.localStorage.setItem(url, localResult);
console.log(result);
})
.catch((err) => {
const localResult = window.localStorage.getItem(url);
if (localResult !== null) {
const parseLocalResult = JSON.parse(localResult)
setData(parseLocalResult);
}
});

return () => {
isCancelled = true;
controller.abort();
};
}, []);

3. Adding async / await to our fetch function

with this easy cool trick you can convert any useEffect hook to an async function

useEffect(
() => async () => {
...
await fetch(url)
...
},[])

The code will look like this

const [data, setData] = useState();
useEffect(() => async ()=>{
let isCancelled = false;
const url = "https://dummyjson.com/products";
const controller = new AbortController();
await fetch(url, { signal: controller.signal })
.then((response) => response.json())
.then((result) => {
const localResult = JSON.stringify(result);
window.localStorage.setItem(url, localResult);
console.log(result);
})
.catch((err) => {
const localResult = window.localStorage.getItem(url);
if (localResult !== null) {
const parseLocalResult = JSON.parse(localResult)
setData(parseLocalResult);
}
});

return () => {
isCancelled = true;
controller.abort();
};
}, []);

3. Using custom react hook

. create a file: ‘useFetch.js’

. copy all the useEffect code with the data state declaration to the new file

. the hook will accept one parameter: ‘url’

useFetch.js

import { useEffect, useState } from "react";
const useFetch = (url) => {
const [data, setData] = useState();
useEffect(
() => async () => {
let isCancelled = false;
const controller = new AbortController();
await fetch(url, { signal: controller.signal })
.then((response) => response.json())
.then((result) => {
const localResult = JSON.stringify(result);
window.localStorage.setItem(url, localResult);
console.log(result);
})
.catch((err) => {
const localResult = window.localStorage.getItem(url);
if (localResult !== null) {
const parseLocalResult = JSON.parse(localResult);
setData(parseLocalResult);
}
});

return () => {
isCancelled = true;
controller.abort();
};
},
[url]
);
return { data };
};
export default useFetch;

Now our <App> code will look cleaner, and we can use our useFetch hook in other components as well

App.js

import useFetch from "../useFetch";
export default function App() {
const url = "https://dummyjson.com/products";
const { data } = useFetch(url);
console.log({ data });
return (
<div>
<h1>How to fetch data from an API in react</h1>
<p>
In this tutorial we will learn how to fetch data from an external API
using React.
</p>
</div>
);
}

5. Showing the data in our UI

In our App.js we will add this code

. Change the “url” variable , this will get only one product from our API

const url = "https://dummyjson.com/products/1";

. Add “.data__container” to “./styles.css”

.data__container {
overflow-wrap: break-word;
border: 1px solid black;
border-radius: 5px;
}

. Add this code to the return ()

<div className="data__container">
{data?.length !==0 ? <p>{JSON.stringify(data)}</p> : <p>No Data</p>}
</div>

“data?.length !==0” : if data is not null or undefined and if the length of data is not null then show the first part else show the second

Read more: optional chaining and ternary operator

The code of “App.js” will look like this

import useFetch from "../useFetch";
import "./styles.css";

export default function App() {
const url = "https://dummyjson.com/products/1";
const { data } = useFetch(url);
return (
<div className="App">
<h1>How to fetch data from an API in react</h1>
<p>
In this tutorial we will learn how to fetch data from an external API
using React.
</p>
<div
className="data__container"
>
{data?.length!==0 ? <p>{JSON.stringify(data)}</p> : <p>No Data</p>}
</div>
</div>
);
}

6. Adding isLoading and error states

. in “useFetch.js” add tow new states

. Begin the useEffect fetch by setting isLoading to false

const useFetch = (url) => {
const [data, setData] = useState();
const [isLoading, setIsLoading] = useState();
const [error, setError] = useState();
useEffect(
() => async () => {
setIsLoading(true);
.....

. in the last “.then()” set isLoading to false and error to null

. in “.catch()” set isLoading to false and update the error state

. Add isLoading and error to the final return after the closure of useEffect

The code of “useFetch.js” will look like this

import { useEffect, useState } from "react";
const useFetch = (url) => {
const [data, setData] = useState();
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState();
useEffect(
() => async () => {
setIsLoading(true);
let isCancelled = false;
const controller = new AbortController();
await fetch(url, { signal: controller.signal })
.then((response) => response.json())
.then((result) => {
const localResult = JSON.stringify(result);
window.localStorage.setItem(url, localResult);
setData(result);
setIsLoading(false);
setError(null);
})
.catch((err) => {
setError(err);
const localResult = window.localStorage.getItem(url);
if (localResult !== null) {
const parseLocalResult = JSON.parse(localResult);
setData(parseLocalResult);
setIsLoading(false);
}
});
return () => {
isCancelled = true;
controller.abort();
};
},
[url]
);
return { data, isLoading, error };
};
export default useFetch;

. In “styles.css” add error class

.error {
color: red;
font-weight: bold;
}

. In you “App.js” add these line of code

if the data is loading, it will show “Loading…”

{isLoading && !error ? <p>Loading...</p> : null}

if there is an error it will show the error message

add error css class

{error ? <p className="error">{error?.message}</p> : null}

The code of “App.js” will look like this

export default function App() {
const url = "https://dummyjson.com/products/1";
const { data, isLoading, error } = useFetch(url);
if (error) return <p>{error}</p>;
return (
<div className="App">
<h1>How to fetch data from an API in react</h1>
<p>
In this tutorial we will learn how to fetch data from an external API
using React.
</p>
<div className="data__container">
{isLoading && !error ? <p>Loading...</p> : null}
{error ? <p className="error">{error?.message}</p> : null}
{data?.length !== 0 ? <p>{JSON.stringify(data)}</p> : <p>No Data</p>}
</div>
</div>
);
}

7. Adding Loading spinner animation

. Add this class to “./styles.css”

Go to https://cssloaders.github.io/ and choose your “loader” CSS style

I will pick this

.loader{
display: block;
position: relative;
height: 20px;
width: 140px;
background-image:
linear-gradient(#FFF 20px, transparent 0),
linear-gradient(#FFF 20px, transparent 0),
linear-gradient(#FFF 20px, transparent 0),
linear-gradient(#FFF 20px, transparent 0);
background-repeat: no-repeat;
background-size: 20px auto;
background-position: 0 0, 40px 0, 80px 0, 120px 0;
animation: pgfill 1s linear infinite;
}

@keyframes pgfill {
0% { background-image: linear-gradient(#FFF 20px, transparent 0), linear-gradient(#FFF 20px, transparent 0), linear-gradient(#FFF 20px, transparent 0), linear-gradient(#FFF 20px, transparent 0); }
25% { background-image: linear-gradient(#FF3D00 20px, transparent 0), linear-gradient(#FFF 20px, transparent 0), linear-gradient(#FFF 20px, transparent 0), linear-gradient(#FFF 20px, transparent 0); }
50% { background-image: linear-gradient(#FF3D00 20px, transparent 0), linear-gradient(#FF3D00 20px, transparent 0), linear-gradient(#FFF 20px, transparent 0), linear-gradient(#FFF 20px, transparent 0); }
75% { background-image: linear-gradient(#FF3D00 20px, transparent 0), linear-gradient(#FF3D00 20px, transparent 0), linear-gradient(#FF3D00 20px, transparent 0), linear-gradient(#FFF 20px, transparent 0); }
100% { background-image: linear-gradient(#FF3D00 20px, transparent 0), linear-gradient(#FF3D00 20px, transparent 0), linear-gradient(#FF3D00 20px, transparent 0), linear-gradient(#FF3D00 20px, transparent 0); }
}

. Add a centre class to “./styles.css”

.centre {
padding: 5px;
margin: auto;
}

Then in “App.js” change the loader code to

...
{isLoading && !error ? <span className="loader centre"></span> : null}
...

8. The complete source code of this tutorial

9. Using Axios

The fetch API is powerful , and it has the added advantage of being readily available in all modern browsers.

But, many developers prefer Axios for its ease of use and advanced futures

. Basic syntax

const url = '__url__';
const data= {
username: "__id__",
password: "__password__"
};

const headers ={
Accept: "application/json",
"Content-Type": "application/json;charset=UTF-8",
};
};
axios
.post(url, data, {headers,})
.then(response) => {
console.log(response?.data);
});

Notice that we don’t need to convert the response to json format, because Axios do it automatically for us

The the result of the API response is stocked in the “data” object

. Response timeout

In Axios, you can use the optional timeout property in the config object to set the number of milliseconds before the request is aborted.

// 10 seconds timeout
axios
.post(url, data,timeout: 10000, {headers,})
.then(response) => {
console.log(response?.data);
});
.then(response => consoole.log(response))
.catch(error => console.error('timeout exceeded'))

. Interceptors

Axios is able to intercept HTTP requests, it’s useful when you need to handle request and response in your HTTP requests ( authentication, refreshing expired JWT tokens, retrying a failed HTTP request, handling errors …)

axios.interceptors.request.use(config => {
// log a message before HTTP request is sent
console.log('Requesting data from server');
return config;
});

axios.get("__url__")
...

. Support for download progress

You can use this tiny module to show a progress bar for your HTTP request

progress-bar-4-axios

. Concurrent HTTP Requests

You can use axios.all() to make concurrent HTTP requests and getting back an equal number of responses in parallel.

const firstRequest = axios.get("__first__url__");
const secondRequest = axios.get("__second__url__");
await axios
.all([firstRequest, secondRequest])
.then(axios.spread((firstResponse, secondResponse) =>{
console.log(firstResponse);
console.log(secondResponse);
}));

10. React Query

React Query gives us caching of server data out of the box with cache invalidation and request deduping. If we use this same query with the same Query Key in another component, it’ll check the cache: if the data is already there, React Query will simply return it, avoiding extra network requests

. Quick Features

  • Transport/protocol/backend agnostic data fetching (REST, GraphQL, promises, whatever!)
  • Auto Caching + Refetching (stale-while-revalidate, Window Refocus, Polling/Realtime)
  • Parallel + Dependent Queries
  • Mutations + Reactive Query Refetching
  • Multi-layer Cache + Automatic Garbage Collection
  • Paginated + Cursor-based Queries
  • Load-More + Infinite Scroll Queries w/ Scroll Recovery
  • Request Cancellation
  • React Suspense + Fetch-As-You-Render Query Prefetching
  • Dedicated Devtools

for further information i suggest you to read more on React Query GitHub page

Conclustion

It was a pleaser for me to write this article and thanks a lot for reading it,

Fetching data from back-end and showing it to the user is the key of success of web application, you have to do it the right way

--

--

Adel Benyahia

Web application developer (HTML │ CSS │ JS | ReactJS | NextJS | NestJS | MERN)