How to Use Fetch API in React

This article provides a straightforward, step-by-step guide on how to implement the Fetch API in React to retrieve data from an external API. You will learn how to make HTTP requests inside functional components using the useEffect hook, manage component state for the fetched data, and handle loading and error states effectively.

The Basic Fetch Implementation

To fetch data in a React functional component, you combine the useState hook (to store the data) and the useEffect hook (to trigger the request when the component mounts).

Here is a complete, minimal example of fetching data from a public API:

import React, { useState, useEffect } from 'react';

function DataFetchingComponent() {
  const [data, setData] = useState([]);

  useEffect(() => {
    fetch('https://jsonplaceholder.typicode.com/posts')
      .then((response) => response.json())
      .then((data) => setData(data))
      .catch((error) => console.error('Error fetching data:', error));
  }, []); // Empty dependency array means this runs once on mount

  return (
    <div>
      <h1>Fetched Posts</h1>
      <ul>
        {data.slice(0, 5).map((post) => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  );
}

export default DataFetchingComponent;

Handling Loading and Error States

In real-world applications, network requests take time and can fail. You must manage loading and error states to provide a good user experience.

Using async/await syntax makes the asynchronous code cleaner and easier to read:

import React, { useState, useEffect } from 'react';

function UserList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchUsers = async () => {
      try {
        const response = await fetch('https://jsonplaceholder.typicode.com/users');
        
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }
        
        const data = await response.json();
        setUsers(data);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };

    fetchUsers();
  }, []);

  if (loading) return <p>Loading users...</p>;
  if (error) return <p>Error: {error}</p>;

  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

export default UserList;

Best Practice: Cleaning Up Requests

If a component unmounts before the fetch request completes, it can cause memory leaks or state updates on unmounted components. To prevent this, use an AbortController to cancel the request if the component unmounts.

useEffect(() => {
  const controller = new AbortController();
  const signal = controller.signal;

  const fetchData = async () => {
    try {
      const response = await fetch('https://jsonplaceholder.typicode.com/users', { signal });
      const data = await response.json();
      setUsers(data);
    } catch (err) {
      if (err.name !== 'AbortError') {
        setError(err.message);
      }
    }
  };

  fetchData();

  // Cleanup function to abort the fetch if the component unmounts
  return () => {
    controller.abort();
  };
}, []);