SmartCodingTips

🔄 Handling Async State in React

Managing state from asynchronous operations like API calls is critical in React. You’ll typically track three things: loading, data, and error — often called the "async state trio."


⚙️ 1. Basic Pattern

Use three pieces of state: isLoading, data, and error.


import { useState, useEffect } from "react";

function Users() {
  const [data, setData] = useState([]);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    async function fetchUsers() {
      try {
        const res = await fetch("https://api.example.com/users");
        if (!res.ok) throw new Error("Network response was not ok");
        const users = await res.json();
        setData(users);
      } catch (err) {
        setError(err.message);
      } finally {
        setIsLoading(false);
      }
    }

    fetchUsers();
  }, []);

🔍 2. Displaying Loading / Error / Data


if (isLoading) return <p>Loading...</p>;
if (error) return <p className="text-red-600">Error: {error}</p>;

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

✨ 3. Tips & Best Practices

  • Initialize isLoading as true by default
  • Always wrap async calls in try/catch
  • Use finally to stop the loading state
  • Optionally use AbortController to cancel fetch on unmount
  • Debounce or throttle rapid updates (e.g., search input)

🔁 4. Create a Reusable Hook


function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    let ignore = false;
    async function fetchData() {
      try {
        const res = await fetch(url);
        if (!res.ok) throw new Error("Error fetching data");
        const json = await res.json();
        if (!ignore) setData(json);
      } catch (err) {
        if (!ignore) setError(err.message);
      } finally {
        if (!ignore) setLoading(false);
      }
    }

    fetchData();
    return () => { ignore = true; };
  }, [url]);

  return { data, loading, error };
}

✅ 5. Summary

  • Track loading, error, and data separately
  • Display each state accordingly to improve UX
  • Extract logic into reusable hooks for cleaner code