All posts
ReactOctober 14, 20268 min read

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.

F
Fepiq Team
Fepiq

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.

What you need before you start

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.

bash
npm create vite@latest my-app -- --template react
cd my-app
npm install
npm run dev

The 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.

The big idea: three steps

Fetching data in React always follows the same pattern:

  1. Create state to hold the data (it starts empty).
  2. Fetch the data once, when the component first appears, using useEffect.
  3. Put the data into state, so React redraws the page with it.

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.

Step 1: Create state for the data

jsx
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.

Step 2: Fetch inside useEffect

We will use JSONPlaceholder, a free fake API made for practice. The address https://jsonplaceholder.typicode.com/users returns a list of ten users.

jsx
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.

Step 3: Show the data on the page

jsx
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.

The complete component

jsx
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.

Add a loading message

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.

jsx
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.

Handle errors

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().

jsx
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.

The same thing with async/await

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.

jsx
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.

Common beginner mistakes

MistakeWhat happensFix
Forgetting the [] at the end of useEffectThe request repeats in an endless loopAdd an empty dependency array []
Calling fetch() directly in the component bodyEndless requests and a slow pagePut it inside useEffect
Making the useEffect callback asyncReact warns you and the effect misbehavesCreate an async function inside and call it
Starting state as null and calling .map()Error: cannot read properties of nullStart with an empty array []
Not checking response.okHTTP errors are treated like successCheck response.ok and throw an error

Where to go next

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 guide

Want to learn how fetch() works in plain JavaScript? Start here.

Read the fetch API guide

Frequently asked questions

How do I fetch data from an API in React?+

Create 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.

Why do I use useEffect to fetch data in React?+

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.

Should I use fetch or axios in React?+

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.

Why is my React fetch running twice?+

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.

How do I show a loading message while data loads in React?+

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 Fepiq

Get new posts in your inbox

Occasional, no-fluff notes on shipping modern software — startups, automation, Laravel, Shopify and more. No spam, unsubscribe anytime.

Keep reading

Related posts

All posts

Let's build something

Ready to ship your next product with Fepiq?

Book a free discovery call. We'll listen, ask sharp questions, and send you a proposal within 3 business days.