Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 64

Fetching Data in useEffect with Fetch/Axios

Recommended resources

Hello! Welcome to the next module in your React journey.

In our last lesson, we built a robust authentication flow with protected routes. However, our login function was just a simulation. In a real application, logging in, fetching user profiles, or loading a list of products all require communication with a backend server over the network. This process is called "working with APIs."

Today, we'll tackle the first and most common task in this area: how to fetch data from a GET endpoint using the fetch or axios API inside a useEffect hook. You'll learn the fundamental pattern for loading external data into your React components, which is a cornerstone for building any dynamic, data-driven application.

The Core Data-Fetching Pattern in React

Before we write any code, let's understand the flow. When you need a component to display data from an API, the process almost always looks like this:

  1. Set up State: Create a state variable (using useState) to hold the data once it arrives. It usually starts as an empty array [] or null.
  2. Trigger the Fetch: Use the useEffect hook to make the API call. To ensure the request is only made once when the component first renders, you'll provide an empty dependency array ([]).
  3. Make the Request: Inside the effect, use a method like fetch() or a library like axios to send a GET request to the API endpoint. Since this is an asynchronous operation, you'll use async/await or Promise .then() chains.
  4. Update State: Once the data is successfully fetched, update your state variable with the received data. This triggers a re-render.
  5. Display the Data: Your component now has the data in its state, and you can map over it to render a list or display the information in your JSX.

Implementing Data Fetching with useEffect and fetch

Let's see this pattern in action. We will use the native browser fetch API, which doesn't require any external libraries. For our data source, we'll use JSONPlaceholder, a free fake API perfect for practicing.

The following video provides an excellent walkthrough of this entire process. While the narrator uses TypeScript (which you might see from the interface Post syntax), the core React and JavaScript logic is exactly the same.

Fetching Data in React - Complete Tutorial

Let's watch a section from the "Fetching Data in React - Complete Tutorial" by Cosden Solutions. This clip demonstrates the fundamental pattern of fetching data inside a useEffect hook when a component mounts.

Watch from 00:42 to 06:25. Pay close attention to: How useState is used to create a posts state variable. The structure of the useEffect hook with an empty dependency array ([]). The creation of an async function (fetchPosts) inside the effect. The use of await fetch() to make the request and await response.json() to parse the data. The final call to setPosts() to update the state and trigger a re-render.


A Common Pitfall: useEffect and async Functions

You might have noticed in the video that the async function was defined inside the useEffect hook, not on the effect itself. This is intentional and very important.

You cannot write useEffect(async () => { ... }).

The reason is that useEffect can optionally return a cleanup function. An async function, by its nature, implicitly returns a Promise. React would see this Promise and try to interpret it as a cleanup function, which would lead to an error.

The correct pattern is always to define a separate async function inside the effect and then call it.

This article provides a clear, concise explanation of this rule and reinforces the importance of the empty dependency array to prevent infinite loops.

How to fetch data with React Hooks

Let's read a short section from the article "How to fetch data with React Hooks" by Robin Wieruch. It clarifies why you can't make the effect callback itself async and how to structure your code correctly.

Read the sections from the beginning down to where the empty dependency array [] is introduced. Focus on the code snippets showing the wrong useEffect(async () => ...) approach and the correct way to fix it by creating an inner fetchData function. The text also explains the infinite re-render loop that occurs without the empty array.


Your Turn: Putting It All Together

Let's consolidate what you've learned into a complete, working example. Here is a component that fetches a list of users from JSONPlaceholder and displays their names.

import { useState, useEffect } from 'react';

function UserList() {
  // 1. Set up state to hold the users data
  const [users, setUsers] = useState([]);

  // 2. Use useEffect to fetch data when the component mounts
  useEffect(() => {
    // 3. Define the async function to perform the fetch
    const fetchUsers = async () => {
      // Use a try...catch block for basic error handling (we'll improve this later)
      try {
        const response = await fetch('https://jsonplaceholder.typicode.com/users');
        const data = await response.json();
        // 4. Update the state with the fetched data
        setUsers(data);
      } catch (error) {
        console.error("Failed to fetch users:", error);
      }
    };

    // Call the function
    fetchUsers();

  }, []); // <-- The empty dependency array ensures this runs only once

  // 5. Render the data
  return (
    <div>
      <h1>User List</h1>
      <ul>
        {users.map(user => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
}

export default UserList;

This code is a template you can adapt for almost any simple data-fetching scenario.


An Alternative: Using axios

While fetch is built-in and works great, many developers prefer a library called axios. It simplifies a few things:

  • It automatically transforms the response to JSON, so you don't need the response.json() step.
  • The actual data is consistently found in a data property on the response object.
  • It has better built-in error handling for network issues.

Let's see a quick comparison.

React Hooks Tutorial - 12 - Fetching data with useEffect Part 1

For a complete picture, let's see how to fetch data using axios. This video from Codevolution walks through the same process but uses axios instead of fetch.

Watch from 00:36 to 05:20. You'll first need to install axios by running npm install axios. As you watch, note the key differences: You import axios at the top. The request is made with axios.get('URL'). The state is updated with response.data. \nThis demonstrates how axios can make the data-fetching code a little more concise.

Choosing between fetch and axios is often a matter of project requirements or team preference. Both are excellent tools to know.


Conclusion

You now have the essential skills to connect your React application to the outside world. This opens the door to building dynamic and interactive user experiences based on real-time data.

Key Takeaways:

  • The useEffect and useState hooks are the primary tools for fetching and storing API data in functional components.
  • To fetch data only once when a component mounts, use useEffect with an empty dependency array ([]).
  • The useEffect callback itself cannot be async. Instead, define an async function inside the effect and call it.
  • The native fetch API is a powerful, built-in tool for making network requests, requiring a manual step to parse the JSON response (response.json()).
  • axios is a popular third-party library that simplifies API calls by automatically handling JSON parsing and providing a more straightforward API.

Next Up:

Our current example works, but it's not very user-friendly. If the network is slow, the user sees a blank screen. If the API request fails, nothing happens. In the next lesson, we will build upon this foundation to manage loading and error states during an API request, providing crucial feedback to the user and making our application more robust.