How to Fetch Data from an API in React Using Hooks

React

readTime

3 min

How to Fetch Data from an API in React Using Hooks

In React, fetching data from an API is a common task that can be done efficiently using hooks. In this post, we’ll create a custom hook, useFetch, which internally uses the useState and useEffect hooks to manage state and handle side effects.


Custom Hook with useState and useEffect

To fetch data from any API using React’s built-in hooks, we can introduce a custom hook called useFetch. This hook will manage the state and lifecycle of the data fetching operation, using useState for tracking the data and loading state, and useEffect for handling the asynchronous API call.

javascript
// useFetch.js
import { useState, useEffect } from "react";

const useFetch = (url) => {
  const [state, setState] = useState([null, false]);

  useEffect(() => {
    setState([null, true]); // Set loading to true before the request

    (async () => {
      const data = await fetch(url).then((res) => res.json());
      setState([data.body, false]); // Set the data and turn off loading
    })();
  }, [url]);

  return state;
};

export default useFetch;

In this function, we return an array with two variables: the state (which holds the data) and the loading flag.

  1. Line 4: We initialize the state with [null, false]null for data and false for the loading status.
  2. Line 8: Once the API call is initiated, we set the loading state to true.
  3. Line 11: After fetching the data using the fetch() function, we update the state by passing the fetched data as the first element and setting the loading flag to false.

It’s important to note that you cannot directly call an asynchronous callback within useEffect. That’s why we define a separate asynchronous function inside the useEffect block.

In this example, we use an IIFE (Immediately Invoked Function Expression). This function is invoked immediately after its definition.

To use this hook in your component, simply call it like this:

javascript
// Import the custom useFetch hook
const Post = () => {
  const [post, loading] = useFetch(
    "https://jsonplaceholder.typicode.com/posts/1"
  );

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

  return <PostBody content={post} />;
};

How to Handle API Errors

When fetching data from an API, you also need to handle potential errors. A traditional approach to handling success and errors in a Promise is to use .then() and .catch().

javascript
import React, { useState, useEffect } from "react";

function FetchAPI() {
  const [users, setUsers] = useState([]);
  const [isError, setIsError] = useState(false);

  useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/users")
      .then((response) => {
        if (response.ok) {
          return response.json();
        } else {
          throw "Error getting users list";
        }
      })
      .then((data) => {
        setUsers(data);
      })
      .catch((error) => {
        setIsError(true);
      });
  }, []);

  return (
    <div>
      {isError ? (
        <h3>Error! Please try again later</h3>
      ) : (
        <ul>
          {users.map((user) => (
            <li key={user.id}>{user.name}</li>
          ))}
        </ul>
      )}
    </div>
  );
}

export default FetchAPI;

In this example, we used .then() to handle the successful data fetching and .catch() to manage errors. Note that the fetch API does not automatically throw errors for HTTP status codes like 404 or 500. You need to manually check for errors by inspecting the ok property of the response object, or by checking the status code directly.


Using async-await with try-catch for Error Handling in useEffect

Another common approach for handling API requests is to use async-await combined with try-catch. This allows for cleaner, more readable code.

javascript
import React, { useState, useEffect } from "react";

function FetchAPI() {
  const [users, setUsers] = useState([]);
  const [isError, setIsError] = useState(false);

  useEffect(() => {
    const fetchData = async () => {
      try {
        let response = await fetch(
          "https://jsonplaceholder.typicode.com/users"
        );
        if (response.status === 200) {
          let data = await response.json();
          setUsers(data);
        } else {
          throw "Error fetching users list";
        }
      } catch (error) {
        setIsError(true);
      }
    };

    fetchData();
  }, []);

  return (
    <div>
      {isError ? (
        <h3>Error! Please try again later</h3>
      ) : (
        <ul>
          {users.map((user) => (
            <li key={user.id}>{user.name}</li>
          ))}
        </ul>
      )}
    </div>
  );
}

export default FetchAPI;

In this example, we replaced .then() with await and wrapped the entire request in a try-catch block. This simplifies the code and provides a more synchronous-like flow. The error handling logic is done inside the catch block, and we check for specific HTTP status codes to determine how to handle different responses.


Key Takeaways

  • Custom Hook: The custom useFetch hook uses useState to store the fetched data and a loading flag, and useEffect to manage the side effects (i.e., making the API call).
  • Error Handling: You can handle errors using .then() and .catch() or async-await with try-catch.
  • Best Practices: Always manage loading states and errors properly to ensure a smooth user experience when fetching data from APIs.

With this approach, you’ll be able to effectively fetch data from APIs using React hooks in a clean, efficient way.

Source: How to use Fetch API with async-await try-catch

authorImg

Witek Pruchnicki

I passionately share knowledge about programming and more in various ways.