React Fetch API
Table of Contents + −
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.
fetchis 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 failureRead it step by step, because each .then does one job.
fetch(url)sends the request and returns a promise for the response.- The first
.thentakes the rawresponseand callsresponse.json(), which reads the body and turns it into a JavaScript value. This also returns a promise. - The second
.thenfinally gets the realdata, the array or object you wanted. .catchruns 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.
usersstarts as an empty array, so the list is empty on the very first render.- After that first render, the effect runs and
fetchasks the API for users. - When the data arrives,
setUsers(data)puts it into state, which triggers a re-render. - Now
usershas the real list, so themapshows one<li>per user.
Output
(empty for a moment, then:)Leanne GrahamErvin HowellClementine 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
fetchthere would fire again and again, in a loop. useEffectwith 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
useEffectis 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
userIdchanges, the effect runs again and fetches that new user. - The URL uses the
userIdvalue, 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 datafetch(url).then((response) => console.log(response));
// ✅ read the body as JSON first, then use itfetch(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
nullfor 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
.thento handle the response when it arrives - ✅ You must call
response.json()first to read the body, then a second.thengives the data - ✅ In React, fetch inside
useEffectand 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
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
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
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
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.