Fetching Data in React

IJAS AHAMMED - Feb 23 - - Dev Community

Fetching data from an API is a fundamental part of web development. In this guide, we’ll explore how to fetch data efficiently using React, handle loading states, manage errors, and prevent race conditions.

📌 Initial Setup

To begin, create a new React application in your code editor. For demonstration purposes, we’ll use the JSONPlaceholder API to fetch posts.

const BASE_URL = "https://jsonplaceholder.typicode.com";
Enter fullscreen mode Exit fullscreen mode

When we hit the /posts endpoint, the API returns data in the following format:

{
  "userId": 1,
  "id": 1,
  "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
  "body": "quia et suscipit..."
}
Enter fullscreen mode Exit fullscreen mode

🏗 Creating State and Interface

Since we’re using TypeScript (which is highly recommended 😜), let’s define an interface for our posts:

interface Post {
  id: number;
  title: string;
}
Enter fullscreen mode Exit fullscreen mode

Next, create a state to store the fetched posts:

const [posts, setPosts] = useState<Post[]>([]);
Enter fullscreen mode Exit fullscreen mode

🔄 Fetching Data with useEffect

To fetch data, we’ll use the useEffect hook, ensuring the request runs once when the component mounts.

useEffect(() => {
  const fetchData = async () => {
    const response = await fetch(`${BASE_URL}/posts`);
    const posts = (await response.json()) as Post[];
    setPosts(posts);
  };
  fetchData();
}, []);
Enter fullscreen mode Exit fullscreen mode

📝 Key Points:

  • The fetch API retrieves data from the endpoint.
  • The response is converted to JSON.
  • The useEffect dependency array is empty, ensuring the function runs only on mount.
  • The fetched posts are stored in state.

🎨 Rendering Posts

Using the map function, we’ll display the posts in a list:

return (
  <div className="App">
    <h1>Data Fetching in React</h1>
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.id}. {post.title}</li>
      ))}
    </ul>
  </div>
);
Enter fullscreen mode Exit fullscreen mode

🔑 Always provide a unique key when rendering lists in React to avoid rendering issues.

⏳ Adding a Loading State

Fetching data takes time, so we should display a loading indicator while waiting for the response.

const [isLoading, setIsLoading] = useState(false);

useEffect(() => {
  const fetchData = async () => {
    setIsLoading(true);
    const response = await fetch(`${BASE_URL}/posts`);
    const posts = (await response.json()) as Post[];
    setPosts(posts);
    setIsLoading(false);
  };
  fetchData();
}, []);
Enter fullscreen mode Exit fullscreen mode

Now, update the UI to show a loading message:

return (
  <div className="App">
    <h1>Data Fetching in React</h1>
    {isLoading ? <div>Loading...</div> : (
      <ul>
        {posts.map((post) => (
          <li key={post.id}>{post.id}. {post.title}</li>
        ))}
      </ul>
    )}
  </div>
);
Enter fullscreen mode Exit fullscreen mode

⚠ Handling Errors

Errors can occur due to network issues or API failures. Let’s handle errors using a state variable and a try-catch block.

const [error, setError] = useState<string | null>(null);

useEffect(() => {
  const fetchData = async () => {
    setIsLoading(true);
    try {
      const response = await fetch(`${BASE_URL}/posts`);
      if (!response.ok) throw new Error("Failed to fetch data");
      const posts = (await response.json()) as Post[];
      setPosts(posts);
    } catch (error: any) {
      setError(error.message);
    } finally {
      setIsLoading(false);
    }
  };
  fetchData();
}, []);
Enter fullscreen mode Exit fullscreen mode

Modify the UI to display errors:

if (error) {
  return <div>Error: {error}</div>;
}
Enter fullscreen mode Exit fullscreen mode

🏎 Handling Race Conditions

Race conditions occur when multiple API calls return in an unexpected order. To prevent this, we use AbortController to cancel previous requests.

const [page, setPage] = useState(1);
const abortControllerRef = useRef<AbortController | null>(null);

useEffect(() => {
  const fetchData = async () => {
    abortControllerRef.current?.abort(); // Cancel previous request
    abortControllerRef.current = new AbortController();
    setIsLoading(true);
    try {
      const response = await fetch(`${BASE_URL}/posts?page=${page}`, {
        signal: abortControllerRef.current.signal,
      });
      const posts = (await response.json()) as Post[];
      setPosts(posts);
    } catch (error: any) {
      if (error.name !== "AbortError") {
        setError(error.message);
      }
    } finally {
      setIsLoading(false);
    }
  };
  fetchData();
}, [page]);
Enter fullscreen mode Exit fullscreen mode

📌 Adding Pagination

We’ll add a button to update the page number, triggering a new API request:

<button onClick={() => setPage((prev) => prev + 1)}>
  Next Page ({page})
</button>
Enter fullscreen mode Exit fullscreen mode

🎉 Conclusion

And that’s it! 🚀 You’ve learned how to fetch data efficiently in React, manage loading states, handle errors, and prevent race conditions.

You can explore the full working example on CodeSandbox. Happy coding! 😃

.