Lesson 16 of 20

Fetching Data

Data Fetching Patterns

Fetching data from APIs is one of the most common tasks in React. The standard pattern combines useEffect (to trigger the fetch) with useState (to store data, loading, and error states).

Always handle three states: loading, success, and error. This ensures a good user experience no matter what happens with the network request.

Example
import { useState, useEffect } from 'react';

function PostList() {
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetch('https://jsonplaceholder.typicode.com/posts?_limit=10')
      .then(res => {
        if (!res.ok) throw new Error('Failed to fetch');
        return res.json();
      })
      .then(data => {
        setPosts(data);
        setLoading(false);
      })
      .catch(err => {
        setError(err.message);
        setLoading(false);
      });
  }, []);

  if (loading) return <p>Loading posts...</p>;
  if (error) return <p>Error: {error}</p>;

  return (
    <ul>
      {posts.map(post => (
        <li key={post.id}>
          <h3>{post.title}</h3>
          <p>{post.body}</p>
        </li>
      ))}
    </ul>
  );
}
  • Use useEffect with [] to fetch data on mount
  • Track loading, data, and error states separately
  • Check res.ok to catch HTTP errors
  • Use .catch() for network errors
  • Show loading spinners and error messages for good UX
Notes
  • For production apps, consider using libraries like TanStack Query (React Query) or SWR which handle caching, refetching, and error handling automatically.