How to Fetch Data in React

Use data fetching in React when a component needs API data, database-backed content, or dynamic external resources after the page loads. The most common pattern is useEffect() plus fetch() and component state.

What you’ll build or solve

You’ll learn how to fetch data in React using useEffect, fetch, and useState. You’ll also know how to handle loading and errors cleanly.

When this approach works best

This approach is the right choice when the component depends on remote data after rendering.

Common real-world scenarios include:

  • User dashboards
  • Product lists
  • Blog feeds
  • Search results
  • Analytics widgets

This is a bad idea when the framework already supports server-side loaders or React Server Components that fetch more efficiently.

Prerequisites

You only need:

  • A React component
  • Basic hooks knowledge
  • An API endpoint

Step-by-step instructions

Step 1: Store loading data in state

Start with state for the data and loading status.

JavaScript

import { useEffect, useState } from "react";

function Users() {
  const [users, setUsers] = useState([]);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    fetch("https://api.example.com/users")
      .then((response) => response.json())
      .then((data) => {
        setUsers(data);
        setIsLoading(false);
      });
  }, []);

  if (isLoading) {
    return <p>Loading...</p>;
  }

  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

export default Users;

The empty dependency array ensures the fetch runs once on mount.

Step 2: Add error handling

Always handle failed requests.

JavaScript

const [error, setError] = useState(null);

useEffect(() => {
  fetch("https://api.example.com/users")
    .then((response) => response.json())
    .then((data) => {
      setUsers(data);
      setIsLoading(false);
    })
    .catch(() => {
      setError("Failed to load users");
      setIsLoading(false);
    });
}, []);

This keeps the UI predictable when APIs fail.

Step 3: Use async/await for cleaner effects

Async logic often reads better with an inner function.

JavaScript

useEffect(() => {
  async function loadUsers() {
    try {
      const response = await fetch("https://api.example.com/users");
      const data = await response.json();
      setUsers(data);
    } catch {
      setError("Failed to load users");
    } finally {
      setIsLoading(false);
    }
  }

  loadUsers();
}, []);

What to look for:

  • Use useEffect() for side-effect fetching
  • Use state for data, loading, and errors
  • Empty dependencies run once
  • Async/await improves readability
  • Always handle failures

Examples you can copy

Product list

JavaScript

useEffect(() => {
  fetch("/api/products")
    .then((response) => response.json())
    .then(setProducts);
}, []);

Search results

JavaScript

useEffect(() => {
  fetch(`/api/search?q=${query}`)

Analytics widget

JavaScript

useEffect(() => {
  loadMetrics();
}, []);

Common mistakes and how to fix them

Mistake 1: Fetching directly in render

What the reader might do:

JavaScript

const data = fetch("/api/users");

Why it breaks: this runs on every render and causes repeated requests.

Corrected approach:

Move the fetch into useEffect().

Mistake 2: Missing dependency logic

What the reader might do:

Use query inside the fetch but keep [].

Why it breaks: the request does not rerun when the query changes.

Corrected approach:

Add the dependency.

Mistake 3: No loading state

What the reader might do:

Render users.map() immediately.

Why it breaks: the data may not exist yet.

Corrected approach:

Track loading separately.

Troubleshooting

If the fetch loops, inspect the dependency array.

If the UI crashes, add loading and null-safe rendering.

If stale data appears, include the correct dependencies.

If the framework supports loaders, compare server-side alternatives.

Quick recap

  • Use useEffect() for data fetching
  • Store data, loading, and errors in state
  • Use async/await for clarity
  • Add correct dependencies
  • Never fetch directly in render