DEV Community

Atilla Baspinar
Atilla Baspinar

Posted on

07 - React: handling http with useEffect

A common pattern in React is to fetch data when a component mounts.

This usually means we need three state values:

  • loading: whether the request is in progress
  • error: whether something went wrong
  • data: the result of the request
import { useEffect, useState } from 'react';

type Post = {
  userId: number;
  id: number;
  title: string;
  body: string;
};

export default function PostList() {
  const [posts, setPosts] = useState<Post[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    const fetchPosts = async () => {
      try {
        setLoading(true);
        setError(null);

        const response = await fetch('https://jsonplaceholder.typicode.com/posts');

        if (!response.ok) {
          throw new Error(`Request failed with status ${response.status}`);
        }

        const data = (await response.json()) as Post[];
        setPosts(data);
      } catch (err) {
        setError(err instanceof Error ? err.message : 'Something went wrong');
      } finally {
        setLoading(false);
      }
    };

    fetchPosts();
  }, []);

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

  if (error) {
    return <p>Error: {error}</p>;
  }

  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>
          <strong>{post.title}</strong>
        </li>
      ))}
    </ul>
  );
}
Enter fullscreen mode Exit fullscreen mode

1) Why this pattern is useful

We use useEffect here because the request is a side effect: it happens after the component renders.

The flow is:

  1. component renders
  2. useEffect starts the request
  3. while loading, we show a loading state
  4. if the request fails, we set an error message
  5. after success or failure, we stop loading

2) Error handling

There are two common cases where a fetch can fail:

  • the network request itself throws an error
  • the server responds with a non-OK status, such as 404 or 500

Both cases should be handled in a try/catch block.

const response = await fetch(url);

if (!response.ok) {
  throw new Error(`Request failed with status ${response.status}`);
}
Enter fullscreen mode Exit fullscreen mode

This makes the error explicit and lets the catch block handle both kinds of errors consistently.

3) finally

finally runs no matter whether the request succeeds or fails.

This is the best place to stop the loading state:

} finally {
  setLoading(false);
}
Enter fullscreen mode Exit fullscreen mode

That way, the UI always exits the loading state after the request completes.

4) Summary

When fetching data in React:

  • create loading, error, and data state
  • start the request in useEffect
  • use try/catch to handle failures
  • manually throw when the HTTP response is not ok
  • stop loading in finally

This pattern is the standard starting point for working with async data in React components.

Top comments (0)