React Fetch Data From an API: Beginner's Guide
Learn how to fetch data in React with useEffect and useState. Simple step-by-step examples with loading and error handling, made for absolute beginners.
Learn how to fetch data in React with useEffect and useState. Simple step-by-step examples with loading and error handling, made for absolute beginners.
How do you fetch data from an API in React? In short: you call fetch() inside the useEffect hook, save the answer in state with useState, and then show that state on the page. That is the whole idea. This guide walks through it slowly, with small examples you can copy and run, even if you have never worked with an API before.
An API (Application Programming Interface) is a web address that sends back data instead of a web page. For example, an API might return a list of users or products. Fetching means asking that address for its data from your code.
You should know what a component is, what state is, and roughly what useEffect does. If those words are new, read our guides first. You also need a React project. A quick way to make one is the Vite tool.
npm create vite@latest my-app -- --template react
cd my-app
npm install
npm run devThe first line creates a new React project called my-app. The next two lines move into the folder and install the tools. The last line starts a local server so you can see your page in the browser.
Fetching data in React always follows the same pattern:
Why not just call fetch() directly inside the component? Because React runs your component function again every time something changes. A direct call would fetch again and again, forever. useEffect lets you say: do this only once.
import { useState, useEffect } from "react";
function UserList() {
const [users, setUsers] = useState([]);
return <p>No users yet</p>;
}Line 1 imports the two hooks we need. Line 4 creates a state variable called users. It starts as an empty array, because we have no data yet. setUsers is the function we will call later to save the data.
We will use JSONPlaceholder, a free fake API made for practice. The address https://jsonplaceholder.typicode.com/users returns a list of ten users.
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/users")
.then((response) => response.json())
.then((data) => setUsers(data));
}, []);Line 2 sends the request. fetch() returns a promise, which is a note that says: the answer will arrive later. Line 3 turns the raw answer into a JavaScript array using response.json(). Line 4 saves that array into state with setUsers. The empty brackets [] on the last line are the dependency array. Empty brackets mean: run this only one time, after the first render.
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);map() loops over the users array and creates one list item for each user. The key prop gives each item a unique id so React can track it. Once setUsers runs, React redraws the component and the names appear.
import { useState, useEffect } from "react";
function UserList() {
const [users, setUsers] = useState([]);
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/users")
.then((response) => response.json())
.then((data) => setUsers(data));
}, []);
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
export default UserList;Save this as UserList.jsx, import it in App.jsx, and add <UserList /> to the page. You should see ten names. This is the core of fetching data in React. The next sections make it more realistic.
Real requests take time. During that time the page is empty, which feels broken. We can add a second piece of state that says whether we are still waiting.
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/users")
.then((response) => response.json())
.then((data) => {
setUsers(data);
setLoading(false);
});
}, []);
if (loading) {
return <p>Loading...</p>;
}loading starts as true. When the data arrives we set it to false. The if statement near the end shows Loading... while loading is true and stops the component from drawing the list early.
Sometimes the internet is down or the server fails. Without error handling, your user sees nothing and has no idea why. Add a third piece of state and a catch().
const [error, setError] = useState(null);
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/users")
.then((response) => {
if (!response.ok) {
throw new Error("Request failed");
}
return response.json();
})
.then((data) => setUsers(data))
.catch((err) => setError(err.message))
.finally(() => setLoading(false));
}, []);
if (error) {
return <p>Something went wrong: {error}</p>;
}response.ok is false when the server answers with an error such as 404 or 500. In that case we throw our own error. Any error, including a network failure, jumps to catch(), which saves the message in state. finally() runs at the end either way, so loading always turns off. Finally, the if (error) check shows a friendly message.
Many people find async/await easier to read than .then() chains. One rule: the function you give to useEffect cannot itself be async, so you create an async function inside it and call it.
useEffect(() => {
async function loadUsers() {
try {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
const data = await response.json();
setUsers(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}
loadUsers();
}, []);await pauses the function until the promise finishes, so the code reads from top to bottom like normal. try runs the risky code, catch handles a failure, and finally always runs. The last line, loadUsers(), actually starts the work.
| Mistake | What happens | Fix |
|---|---|---|
| Forgetting the [] at the end of useEffect | The request repeats in an endless loop | Add an empty dependency array [] |
| Calling fetch() directly in the component body | Endless requests and a slow page | Put it inside useEffect |
| Making the useEffect callback async | React warns you and the effect misbehaves | Create an async function inside and call it |
| Starting state as null and calling .map() | Error: cannot read properties of null | Start with an empty array [] |
| Not checking response.ok | HTTP errors are treated like success | Check response.ok and throw an error |
Once this works, try fetching a different list, such as posts or todos, and show more fields. Later you can learn about libraries that handle loading and caching for you, but understanding this basic pattern first will make everything else easier.
New to useEffect? Read our beginner guide to understand why the empty brackets matter.
Read the useEffect guideWant to learn how fetch() works in plain JavaScript? Start here.
Read the fetch API guideCreate state with useState, call fetch() inside useEffect with an empty dependency array, and save the result with the state setter. React then redraws the component with the new data.
React runs your component function many times. useEffect lets you run code, like a request, only at chosen moments, such as once when the component first appears. Without it you could send the same request over and over.
For beginners, fetch is the best choice because it is built into the browser and needs no installation. Axios is a popular library that adds small conveniences, but you do not need it to start.
In development, React StrictMode runs effects twice on purpose to help find bugs. This does not happen in the production build, so it is normal and you can ignore it while learning.
Add a loading state that starts as true. Set it to false after the data arrives or an error happens, and return a Loading message while it is true.
Need help turning what you learn into a real web app? Our team is happy to talk.
Talk to FepiqOccasional, no-fluff notes on shipping modern software — startups, automation, Laravel, Shopify and more. No spam, unsubscribe anytime.
Keep reading
Learn SQL CASE WHEN step by step. This beginner guide shows how to label, group and sort data with simple examples you can copy and run today.
PostgreSQL ILIKE explained for beginners: search text without worrying about case, learn % and _ wildcards, and copy simple examples you can run today.
Let's build something
Book a free discovery call. We'll listen, ask sharp questions, and send you a proposal within 3 business days.