Handling API Calls with useEffect and fetch

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:

  1. Trigger the API call when the component mounts.
  2. Handle loading states to let users know data is being fetched.
  3. Handle errors if something goes wrong during the fetch.
  4. 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

  1. State Management:
    • We use three pieces of state: dataloading, and error.
    • data will hold the data fetched from the API.
    • loading will be true while we’re fetching data and false once we’ve finished.
    • error will hold any error messages if something goes wrong during the fetch.
  2. Using useEffect:
    • We define the fetchData function inside the useEffect 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.
  3. Making the API Call:
    • Inside the fetchData function, we use await 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 the data state.
  4. 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.
  5. Displaying the Data:
    • Once the data is loaded, we map through the data array and display the titles of the posts.

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.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *