Published on 2.6.2019
Fetching data from an API is a common requirement in modern web development. React’s useEffect hook, combined with the native fetch function, provides a simple and effective way to handle asynchronous operations in functional components. Whether you’re fetching data to populate a list, load user profiles, or gather any kind of external data, combining these tools will make your life much easier.
In this tutorial, we’ll explore how to use useEffect with fetch to make API calls, handle loading states, and manage errors. We’ll also see how to use async/await to keep the code clean and readable.
What Is useEffect?
Before diving into making API calls, let’s quickly recap what useEffect is and why it’s useful.
The useEffect hook allows you to perform side effects in your functional components. Side effects include things like fetching data, updating the DOM, setting up subscriptions, and more.
Unlike lifecycle methods in class components, useEffect runs after the component renders, so you can safely run any side-effect code after the initial render.
Why Combine useEffect and fetch?
When you fetch data in a React component, you need to:
- Trigger the API call when the component mounts.
- Handle loading states to let users know data is being fetched.
- Handle errors if something goes wrong during the fetch.
- Update the component’s state when the data is successfully fetched.
By combining useEffect with fetch, you can handle all of this in a clean, declarative manner.
How to Fetch Data Using fetch Inside useEffect
The fetch function is used to make HTTP requests to retrieve data. In this example, we will fetch data from a public API and display it in a component. We’ll also handle the loading and error states.
Here’s how you can do it:
import React, { useState, useEffect } from 'react';
function DataFetching() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
// Define the async function inside useEffect
const fetchData = async () => {
try {
// Start the loading state
setLoading(true);
setError(null);
// Fetch the data
const response = await fetch('https://jsonplaceholder.typicode.com/posts');
if (!response.ok) {
throw new Error('Network response was not ok');
}
// Parse the JSON response
const data = await response.json();
// Update state with the fetched data
setData(data);
} catch (error) {
// Handle any errors during fetch
setError(error.message);
} finally {
// Set loading to false when the fetch is done
setLoading(false);
}
};
fetchData();
}, []); // Empty dependency array ensures this runs once when the component mounts
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div>
<h1>Fetched Data</h1>
<ul>
{data.map(item => (
<li key={item.id}>{item.title}</li>
))}
</ul>
</div>
);
}
export default DataFetching;
Breaking It Down
- State Management:
- We use three pieces of state:
data,loading, anderror. datawill hold the data fetched from the API.loadingwill betruewhile we’re fetching data andfalseonce we’ve finished.errorwill hold any error messages if something goes wrong during the fetch.
- We use three pieces of state:
- Using
useEffect:- We define the
fetchDatafunction inside theuseEffecthook. This ensures that the function is only called once when the component mounts. - The
[]dependency array tells React that this effect has no dependencies, so it will only run once on mount.
- We define the
- Making the API Call:
- Inside the
fetchDatafunction, we useawait fetch(...)to make an asynchronous HTTP GET request. - We handle the response by first checking if the request was successful with
response.ok. If it wasn’t, we throw an error. - Once the data is fetched, we use
response.json()to parse the JSON response and store it in thedatastate.
- Inside the
- Handling Loading and Errors:
- While the data is being fetched, we display a “Loading…” message.
- If there’s an error during the fetch (like a network issue or a problem with the API), we display the error message.
- Displaying the Data:
- Once the data is loaded, we map through the
dataarray and display the titles of the posts.
- Once the data is loaded, we map through the
Using async/await for Clean Code
Notice how we used async/await inside useEffect. The async keyword makes the function asynchronous, so you can use await within it to wait for promises (like fetch or response.json()) to resolve. This keeps our code clean and readable without nesting multiple .then() calls.
The try/catch block allows us to catch any errors that occur during the fetch, ensuring that we handle errors gracefully.
Handling Multiple API Calls
If you need to make multiple API calls, you can either call them sequentially inside useEffect or run them in parallel using Promise.all. Here’s an example of how to make two API calls at the same time:
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
setError(null);
const [postsResponse, usersResponse] = await Promise.all([
fetch('https://jsonplaceholder.typicode.com/posts'),
fetch('https://jsonplaceholder.typicode.com/users')
]);
if (!postsResponse.ok || !usersResponse.ok) {
throw new Error('One or both responses were not ok');
}
const posts = await postsResponse.json();
const users = await usersResponse.json();
setData({ posts, users });
} catch (error) {
setError(error.message);
} finally {
setLoading(false);
}
};
fetchData();
}, []);
Here, Promise.all is used to fetch both posts and users at the same time, and we only need one await to wait for both requests to resolve.
Conclusion
Handling API calls in React is a fundamental task that every React developer needs to understand. With useEffect and fetch, you can easily make asynchronous requests, manage loading and error states, and update your components with fetched data.
By using async/await inside useEffect, you can keep your code clean, readable, and easy to maintain. Whether you’re dealing with a single API call or multiple, React’s hooks make managing side effects more straightforward than ever.