Back to Blog
Lesson 25 of the React Fundamentals: Build Modern UIs from Scratch course
ReactJune 25, 20263 min read

Fetching Data from an API: A Practical React Guide

Master the fetch API in React to retrieve external data. Learn to perform asynchronous requests and store JSON responses in your component state effectively.

ReactuseEffectuseStatefetchasynchronousAPIjavascriptfrontend

Previously in this course, we covered introduction-to-side-effects-managing-external-logic-in-react and mastering-useeffect-dependencies-control-your-react-lifecycle. Those lessons established that a side effect is any operation that reaches outside of React's render cycle, such as talking to a server.

In this lesson, we will move from static data to real-world connectivity. You will learn to use the fetch API to retrieve movie data from a remote server, handle the asynchronous nature of network requests, and update your UI by saving that data into component state.

Why Data Fetching is Asynchronous

When you request data from an API, your JavaScript code doesn't stop and wait for the server to reply. If it did, your entire browser would "freeze" until the data arrived. Instead, network requests are asynchronous—the browser sends the request, continues executing other code, and handles the response later when it arrives.

In React, we manage this lifecycle by triggering the request in a useEffect hook and storing the result in useState.

Performing Asynchronous Requests

To fetch data, we use the browser's native fetch() function. This function returns a Promise. We use the async/await syntax to handle the response cleanly, turning the raw stream of bytes from the server into usable JSON data.

Here is how we integrate this into our movie-browser project.

Worked Example: Fetching Movies

In your MovieBrowser.jsx component, we will initialize an empty array in state and fetch data as soon as the component mounts:

JSX
import { useState, useEffect } from CE9178">'react';

function MovieBrowser() {
  const [movies, setMovies] = useState([]);

  useEffect(() => {
    // Define an async function to handle the request
    async function fetchData() {
      try {
        const response = await fetch(CE9178">'https://api.example.com/movies');
        const data = await response.json();
        setMovies(data);
      } catch (error) {
        console.error("Failed to fetch movies:", error);
      }
    }

    fetchData();
  }, []); // Empty array ensures this runs only once on mount

  return (
    <ul>
      {movies.map(movie => (
        <li key={movie.id}>{movie.title}</li>
      ))}
    </ul>
  );
}

Breaking Down the Code

  1. useState([]): We initialize our state as an empty array because we expect a collection of movies.
  2. useEffect with []: By passing an empty dependency array, we tell React to run this effect exactly once: when the component first appears on the screen.
  3. async/await: The fetch function returns a promise. await pauses the function execution until the promise resolves.
  4. response.json(): The response object represents the HTTP response. We call .json() to parse the body into a JavaScript object.

While this looks straightforward, understanding how to manage fetch-priority-api-optimize-resource-prioritization-for-core-web-vitals can help you further optimize how these requests are handled in high-traffic applications.

Hands-on Exercise

Modify your MovieBrowser component to fetch data from a placeholder API. You can use https://jsonplaceholder.typicode.com/posts (or a similar public JSON endpoint) if you don't have your own movie API ready yet.

  1. Create a state variable called items.
  2. Use useEffect to fetch the data from the URL.
  3. Update the state with the returned array.
  4. Render the list using .map() just like we learned in rendering-lists-of-data-in-react-a-practical-guide.

Common Pitfalls

  • Forgetting the Dependency Array: If you omit the [] in useEffect, the effect will run after every render. This leads to an infinite loop: the component renders, the effect updates the state, the state change triggers a re-render, and the cycle repeats.
  • Assuming Data Exists Immediately: On the first render, your state is the initial value (e.g., []). Your component must be able to render without crashing if the data hasn't arrived yet.
  • Ignoring Errors: A fetch call only rejects if the request itself fails (like a network timeout). If the server returns a 404 or 500 error, fetch will not throw an error. You must check response.ok before calling .json().

Recap

Fetching data is the bridge between your UI and the outside world. By using useEffect with an empty dependency array, you ensure your data is requested only once upon mounting. By using async/await, you handle the asynchronous nature of the web gracefully, allowing you to update your state and re-render your components as soon as the data is ready.

Up next: We will address the "flicker" of empty screens by learning how to handle loading states while our data is in flight.

Similar Posts