Fetching Data in Effects

Load data from an API inside useEffect and store it in state.

SyntaxuseEffect(() => { fetchData(); }, [id]);

A classic way to fetch data is inside useEffect. When the component mounts (or a dependency changes), you call the API and save the result in state.

Avoiding race conditions

If the input changes quickly, an older request might resolve after a newer one. Use an ignore flag in the cleanup so a stale response is discarded.

Example

Example Β· javascript
import { useState, useEffect } from 'react';

function User({ id }) {
  const [user, setUser] = useState(null);
  useEffect(() => {
    let ignore = false;
    fetch('/api/users/' + id)
      .then((r) => r.json())
      .then((data) => {
        if (!ignore) setUser(data);
      });
    return () => { ignore = true; };
  }, [id]);
  return <p>{user ? user.name : 'Loading...'}</p>;
}

When to use it

  • A user profile page fetches account details inside useEffect when the component mounts and re-fetches automatically whenever the userId prop changes.
  • A blog post view cancels the in-flight API call via AbortController when the user navigates to a different post before the first request completes, preventing stale data from appearing.
  • A search-results page debounces user input and triggers a fresh fetch inside useEffect each time the debounced query string changes.

More examples

Basic fetch on mount

Fetches data once on mount with an empty dependency array and stores the result in state.

Example Β· jsx
import { useState, useEffect } from 'react';

function PostList() {
  const [posts, setPosts] = useState([]);

  useEffect(() => {
    fetch('/api/posts')
      .then(r => r.json())
      .then(setPosts);
  }, []);

  return <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>;
}

Refetch when dependency changes

Re-runs the effect when userId changes and cleans up with AbortController to avoid race conditions.

Example Β· jsx
import { useState, useEffect } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    const controller = new AbortController();
    fetch(`/api/users/${userId}`, { signal: controller.signal })
      .then(r => r.json())
      .then(setUser)
      .catch(() => {});
    return () => controller.abort();
  }, [userId]);

  return user ? <h2>{user.name}</h2> : <p>Loading...</p>;
}

Race condition fix with ignore flag

Uses an ignore flag in the cleanup to discard results from stale fetches when the query changes rapidly.

Example Β· jsx
import { useState, useEffect } from 'react';

function SearchResults({ query }) {
  const [results, setResults] = useState([]);

  useEffect(() => {
    let ignore = false;
    fetch(`/api/search?q=${encodeURIComponent(query)}`)
      .then(r => r.json())
      .then(data => { if (!ignore) setResults(data); });
    return () => { ignore = true; };
  }, [query]);

  return <ul>{results.map(r => <li key={r.id}>{r.title}</li>)}</ul>;
}

Discussion

  • Be the first to comment on this lesson.