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
. data
will hold the data fetched from the API.loading
will betrue
while we’re fetching data andfalse
once we’ve finished.error
will hold any error messages if something goes wrong during the fetch.
- We use three pieces of state:
- Using
useEffect
:- We define the
fetchData
function inside theuseEffect
hook. 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
fetchData
function, 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 thedata
state.
- 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
data
array 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.