DEV Community

Cover image for Fetching Data in React
IJAS AHAMMED
IJAS AHAMMED

Posted on

1

Fetching Data in React

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! 😃

Heroku

This site is built on Heroku

Join the ranks of developers at Salesforce, Airbase, DEV, and more who deploy their mission critical applications on Heroku. Sign up today and launch your first app!

Get Started

Top comments (1)

Collapse
 
webjose profile image
José Pablo Ramírez Vargas

If you want a clean and fully typed fetch call, try my dr-fetch package.

BTW, you should do if (!response.ok) setError("Failed to fetch data"); instead of throwing. Throwing has a higher cost than branching.

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay