Writing Your Own useFetch Hook in React
Tags: react, javascript, webdev, programming
Published: 2021-01-05 12:40:58

---
title: Writing Your Own useFetch Hook in React
published: true
description: React Hooks have been all the rage for a little over a year. Let's see how we can roll our own useFetch hook to abstract fetch request logic out of our components.
tags: react, javascript, webdev, programming
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/qx0dm4qcyw3g2sf5omlf.jpg
canonical_url: https://typeofnan.dev/writing-your-own-usefetch-hook-in-react/
---

React Hooks have been all the rage for a little over a year. Let's see how we can roll our own `useFetch` hook to abstract fetch request logic out of our components.

**Note:** This is for academic purposes only. You _could_ roll your own `useFetch` hook and use it in production, but I would highly recommend using an established library like [use-http](https://use-http.com) to do the heavy lifting for you!

<hr />

### If you enjoy this post, please give it a 💓, 🦄, or 🔖 and consider signing up for 📬 [my free weekly dev newsletter](https://buttondown.email/devtuts?hooks_dev)

<hr />

# Our useFetch Function Signature

To determine our `useFetch` function signature, we should consider the information we might need from the end user to actually execute our fetch request. In this case, we'll say that we need the resource `url` and we need the `options` that might go along with the request (e.g., request method).

```javascript
function useFetch(initialUrl, initialOptions) {
  // Hook here
}
```

In a more full-featured solution, we might give the user a way ot abort the request, but we're happy with our two arguments for now!

# Maintaining State in Our Hook

Our hook is going to need to maintain some state. We will _at least_ need to maintain `url` and `options` in state (as we'll need to give our user a way to `setUrl` and `setOptions`). There are some other stateful variable's we'll want as well!

- data (the data returned from our request)
- error (any error if our request fails)
- loading (a boolean indicating whether we are actively fetching)

Let's create a bunch of stateful variables using the built-in `useState` hook. also, we're going to want to give our users the chance to do the following things:

- set the url
- set options
- see the retrieved data
- see any errors
- see the loading status

Therefore, we must make sure to return those two state setting functions and three data from our hook!

```javascript
import { useState } from 'React';

function useFetch(initialUrl, initialOptions) {
  const [url, setUrl] = useState(initialUrl);
  const [options, setOptions] = useState(initialOptions);
  const [data, setData] = useState();
  const [error, setError] = useState();
  const [loading, setLoading] = useState(false);

  // Some magic happens here

  return { data, error, loading, setUrl, setOptions };
}
```

Importantly, we default our `url` and `options` to the `initialUrl` and `initialOptions` provided when the hook is first called. Also, you might be thinking that these are a lot of different variables and you'd like to maintain them all in the same object, or a few objects&mdash;and that would be totally fine!

# Running an Effect When our URL or Options Change

This is a pretty important part! We are going to want to execute a `fetch` request every time the `url` or `options` variables change. What better way to do that than the built-in `useEffect` hook?

```javascript
import { useState } from 'React';

function useFetch(initialUrl, initialOptions) {
  const [url, setUrl] = useState(initialUrl);
  const [options, setOptions] = useState(initialOptions);
  const [data, setData] = useState();
  const [error, setError] = useState();
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    // Fetch here
  }, [url, options]);

  return { data, error, loading, setUrl, setOptions };
}
```

# Calling Fetch with Async Await

I like async/await syntax over Promise syntax, so let's use the former! This, of course, works just as well using `then`, `catch`, and `finally` rather than async/await.

```javascript
import { useState } from 'React';

function useFetch(initialUrl, initialOptions) {
  const [url, setUrl] = useState(initialUrl);
  const [options, setOptions] = useState(initialOptions);
  const [data, setData] = useState();
  const [error, setError] = useState();
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    setLoading(true);
    setError(undefined);

    async function fetchData() {
      try {
        const res = await fetch(url, options);
        const json = await res.json();
        setData(json);
      } catch (e) {
        setError(e);
      }
      setLoading(false);
    }
    fetchData();
  }, [url, options]);

  return { data, error, loading, setUrl, setOptions };
}
```

That was a lot! Let's break it down a bit. When we run our effect, we know that we're starting to fetch data. Therefore we set our `loading` variable to `true` and we clear our any errors that may have previously existed.

In our async function, we wrap our `fetch` request code with a `try/catch` block. Any errors we get we want to report to the user, so in our `catch` block we `setError` to whatever error is reported.

In our `try` block, we do a fairly standard `fetch` request. We _assume_ our data being returned is `json` because I'm lazy, but if we were trying to make this the most versatile hook we would probably give our users a way to configure the expected response type. Finally, assuming all is successful, we set our `data` variable to our returned JSON!

# Using The Hook

Believe it or not, that's all there is to creating our custom hook! Now we just need to bring it into a sample app and hope that it works.

In the following example, I have an app that loads any github user's basic github profile data. This app flexes almost all the features we designed for our hook, with the exception of setting `fetch` options. We can see that, while the fetch request is being loaded, we can display a "Loading" indicator. When the fetch is finished, we either display a resulting error or a stringified version of the result.

We offer our users a way to enter a different github username to perform a new fetch. Once they submit, we use the `setUrl` function exported from our `useFetch` hook, which causes the effect to run and a new request to be made. We soon have our new data!

```jsx
const makeUserUrl = user => `https://api.github.com/users/${user}`;

function App() {
  const { data, error, loading, setUrl } = useFetch(makeUserUrl('nas5w'));
  const [user, setUser] = useState('');

  return (
    <>
      <label htmlFor="user">Find user:</label>
      <br />
      <form
        onSubmit={e => {
          e.preventDefault();
          setUrl(makeUserUrl(user));
          setUser('');
        }}
      >
        <input
          id="user"
          value={user}
          onChange={e => {
            setUser(e.target.value);
          }}
        />
        <button>Find</button>
      </form>
      <p>{loading ? 'Loading...' : error?.message || JSON.stringify(data)}</p>
    </>
  );
}
```

Feel free to check out the `useFetch` hook and sample application [on codesandbox here](https://codesandbox.io/s/use-fetch-hook-k2xke).

# Concluding Thoughts

Writing a custom React hook can be a fun endeavor. It's sometimes a bit tricky at first, but once you get the hang of it it's quite fun, and can result in really shortening and reducing redundancy in your component code.

If you have any questions about this hook, React, or JS in general, don't hesitate to [reach out to me on Twitter](https://twitter.com/nas5w)!