React Fetch API

In the last lesson, React Introduction to APIs, you learned what an API is and how the request-response flow works. Now you’ll use fetch inside a React component to call a real API, get JSON back, and show it on the screen.

🤔 Why fetch?

You need to ask a server for data from your JavaScript, and you don’t want to install anything just to start. That’s where fetch shines.

  • fetch is built into every modern browser, so there’s nothing to install.
  • It sends a request to a URL and hands you back the response.
  • It works smoothly with promises, so you can react when the data arrives.
  • It’s the standard starting point before you ever reach for an outside library.

So fetch is the simplest real way to call an API in React. It’s already there, waiting to be used.

🧩 The basic shape of fetch

Before putting it in React, let’s see the plain shape. fetch returns a promise, so we chain .then steps to handle the response when it arrives.

fetch("https://jsonplaceholder.typicode.com/users")
.then((response) => response.json()) // turn the response into JSON
.then((data) => console.log(data)) // now use the data
.catch((error) => console.log(error)); // handle any failure

Read it step by step, because each .then does one job.

  • fetch(url) sends the request and returns a promise for the response.
  • The first .then takes the raw response and calls response.json(), which reads the body and turns it into a JavaScript value. This also returns a promise.
  • The second .then finally gets the real data, the array or object you wanted.
  • .catch runs if anything goes wrong, like the network being down.

Why two .then steps?

fetch gives you a response object first, not the data. The body has to be read and parsed, and that takes a moment, so response.json() returns its own promise. That’s why you need a second .then to get the actual data.

💡 Fetching inside a React component

Now let’s put it where it belongs. In React, you fetch inside useEffect so it runs after the component shows up, and you store the result in state so the screen updates.

import { useState, useEffect } from "react";
function UserList() {
const [users, setUsers] = useState([]); // start with an empty list
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/users")
.then((response) => response.json())
.then((data) => setUsers(data)); // save the data into state
}, []); // empty array: run once after the first render
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}

Follow the whole journey of the data.

  • users starts as an empty array, so the list is empty on the very first render.
  • After that first render, the effect runs and fetch asks the API for users.
  • When the data arrives, setUsers(data) puts it into state, which triggers a re-render.
  • Now users has the real list, so the map shows one <li> per user.

Output

(empty for a moment, then:)
Leanne Graham
Ervin Howell
Clementine Bauch
... and so on

🧠 Why fetch goes inside useEffect

You might wonder why we don’t just call fetch directly in the component body. Here’s why that matters.

  • The component body runs on every render. A fetch there would fire again and again, in a loop.
  • useEffect with an empty [] runs the fetch only once, after the first render.
  • Fetching is a side effect, talking to the outside world, which is exactly what useEffect is for.

So the rule is simple. Data fetching is a side effect, so it belongs in useEffect, not in the render body.

Never fetch directly in the body

Calling fetch straight in the component body causes an infinite loop: the fetch updates state, the state change re-renders, the render fetches again. Always put it inside useEffect with the right dependency array.

🔄 Fetching when something changes

Sometimes you want to refetch when a value changes, like loading a different user by id. You put that value in the dependency array.

function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
.then((response) => response.json())
.then((data) => setUser(data));
}, [userId]); // re-run whenever userId changes
if (!user) return <p>No user yet.</p>;
return <h2>{user.name}</h2>;
}

Here’s what the dependency array buys you.

  • The effect runs once at the start for the first userId.
  • Whenever userId changes, the effect runs again and fetches that new user.
  • The URL uses the userId value, so each request asks for the right person.

So [] means fetch once, and [userId] means fetch again every time the id changes. The dependency array controls when the request happens.

⚠️ Common Mistakes

The most common slip is forgetting response.json() and trying to use the raw response as data.

// ❌ this logs the Response object, not your data
fetch(url).then((response) => console.log(response));
// ✅ read the body as JSON first, then use it
fetch(url)
.then((response) => response.json())
.then((data) => console.log(data));

Watch out for these.

  • Don’t skip response.json(). The first value is a Response object, not the data inside it.
  • Don’t forget the dependency array, or use the wrong one. An empty [] fetches once; a missing array fetches on every render.
  • Don’t fetch in the component body. It belongs in useEffect.

✅ Best Practices

A few habits for clean fetching, before we add loading and error states next.

  • Always fetch inside useEffect, with a dependency array that matches when you want it to run.
  • Always call response.json() to read the body before using the data.
  • Start state as something sensible, an empty array for a list or null for a single item.
  • Keep the data in state, so the screen updates when it arrives.

One thing is still missing

Right now we just hope the request works. Real apps also show a loading message while waiting and an error message if it fails. You’ll add both very soon. This lesson is about getting the data in the first place.

🧩 What You’ve Learned

  • fetch(url) is the browser’s built-in way to call an API, no install needed
  • ✅ It returns a promise, so you chain .then to handle the response when it arrives
  • ✅ You must call response.json() first to read the body, then a second .then gives the data
  • ✅ In React, fetch inside useEffect and save the result with a state setter
  • ✅ An empty [] fetches once; putting a value like [userId] refetches when it changes
  • ✅ Never fetch in the component body, or you create an infinite loop

Check Your Knowledge

Test what you learned. Pick an answer for each question, then click Check.

  1. 1

    Why do you need two .then steps with fetch?

    Why: fetch resolves to a Response object first. Calling response.json() reads the body and parses it, and that returns its own promise, so a second .then gives you the actual data.

  2. 2

    Where should you call fetch in a React component?

    Why: Fetching is a side effect. Putting it in the body runs it on every render and causes an infinite loop. useEffect runs it at the right time instead.

  3. 3

    What does an empty dependency array [] do for a fetch in useEffect?

    Why: An empty array means the effect runs once after the first render, which is exactly what you want for loading data a single time.

  4. 4

    What happens if you skip response.json()?

    Why: fetch gives you a Response object first. Without response.json() to read and parse the body, you never reach the real data.

🚀 What’s Next?

Now you can fetch data and show it. But all those .then chains can get hard to read. Next you’ll learn a cleaner way to write the same thing using async and await.

React Async and Await

Share & Connect