React custom hook: useFetch

useFetch

One common task in web development is fetching data from an API, and React provides a convenient way to do this with the useEffect hook. However, fetching data can be a repetitive task, so in this blog post, we’ll learn how to create a custom hook called useFetch that makes fetching data even more convenient by using the JavaScript fetch API.

The idea of useFetch is to fetch data from an API endpoint, usually via HTTP get, and return its results to the calling components. We are looking for the following requirements:

  1. The custom hook is called useFetch, and it uses JavaScript’s fetch API.
  2. It will take two parameters: a URL to fetch data from, and an optional headers object to pass along with the request that may include setting the content type, or authentication token.
  3. For the output we need three things:
    error: if there’s any error, this would contain the error message
    loading: it’s a boolean to indicate whether the API has been called and is processing (true) or is ready (false)
    data: this contains the final data returned from the custom hook.

So let’s start with a custom hook template.

import { useState, useEffect } from 'react';

export default function useFetch(url, headers) {
  const [error, setError] = useState<string>("");
  const [loading, setLoading] = useState<boolean>(false);
  const [data, setData] = useState<any>();

....

  return { error, loading, data };
}

The above code gives the custom hook a basic structure. We set the three state variables using useState (error, loading, and data) and return them at the bottom of the file.

Now we’ll create a function that does the actual fetching of the API:

  const getData = () => {
    setLoading(true);
    fetch(url, { headers })
      .then((res) => {
        if (res.ok) return res.json();
        setError("Error occurred fetching data");
      })
      .then((data) => setData(data))
      .catch((error) => {
        setError(error.message);
      })
      .finally(() => {
        setLoading(false);
      });
  };

In this function, getData, we first set the state of loading to true, meaning we are starting the fetching process, then we execute the fetch command with the url and headers passed in. Note that we are using res.ok to check for errors instead of res.status because even when there is an error, such as 404, the fetch command is still considered passing. That’s why we use res.ok which makes more sense to check for errors.

Now we have a function that handles the fetching of the data, we want to call it. We will use React’s useEffect hook to do that.

  useEffect(() => {
    if (url) {
      getData();
    } else {
      setError("URL is not specified");
    }
  }, [url]);

What we are telling React is when we have a new url passed into the custom hook, it calls the getData() function to get the data from the API, sets necessary states for error, loading, and data, then passes everything back to the calling component.

The final and completed useFetch custom hook:

import { useState, useEffect } from "react";

export default function useFetch(url: string, headers?: HeadersInit) {
  const [error, setError] = useState<string>("");
  const [loading, setLoading] = useState<boolean>(false);
  const [data, setData] = useState<any>();

  const getData = () => {
    setLoading(true);
    fetch(url, { headers })
      .then((res) => {
        if (res.ok) return res.json();
        setError("Error occurred fetching data");
      })
      .then((data) => setData(data))
      .catch((error) => {
        setError(error.message);
      })
      .finally(() => {
        setLoading(false);
      });
  };

  useEffect(() => {
    if (url) {
      getData();
    } else {
      setError("URL is not specified");
    }
  }, [url]);

  return { error, loading, data };
}

From the above code, you can see that we have typed headers with HeadersInit. In a nutshell, HeadersInit is an interface representing a collection of HTTP headers. It’s used to initialize the Headers object that is sent with a request to a server or received in response from a server.

Now that we have created the custom hook, we can use it in our components to fetch data from APIs. Here’s an example of how we might do it.

import useFetch from "./hooks/useFetch";

export default function Component() {
  const { error, loading, data } = useFetch(
    "https://jsonplaceholder.typicode.com/users"
  );

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

  if (data !== undefined) {
    console.log("data", data);
  }

.....
}

We started by importing useFetch, then destruct the three states it gives us. If there is an error or api is still loading, we would see that on the screen. You can choose to show the error on the screen or use another method to handle it. The loading state should change from true (when it starts) to false (when it finishes), so you’d see a brief flashing message of “Loading…” on the screen. When everything works and you get the data back, you can process the data however you wish in your return statements.

You Might Also Like