Integrating Axios with React Hooks
Tags: javascript, react, axios, hooks
Published: 2022-03-29 11:15:27

by [Arek Nawo](https://blog.openreplay.com/authors/arek-nawo)

[Axios](https://axios-http.com/) is one of the most popular HTTP JavaScript client libraries - for many reasons. First of all, it’s **isomorphic**, meaning you can use it the same way, whether you’re running in a browser or in Node.js, even though HTTP APIs are different for both. On top of that, Axios is Promise-based, integrating well with modern `async`/`await` JavaScript syntax.

Thanks to the mentioned features and others, such as automatic JSON transforms or built-in request cancellation, Axios is a perfect choice for both your front end and back end. To give you an example of that, in this article you’ll see how you can integrate Axios with your React app by creating a custom React Hook. You’ll learn to use Axios as well as how to build a comfortable wrapper around it.

## Async in React Hooks

[React Hooks](https://reactjs.org/docs/hooks-reference.html) provide a simple, functional way of building stateful React components. They’re easily composable and can be used to wrap different APIs or business logic into easily digestible functions.

Working with hooks gets a bit more complicated when dealing with asynchronous code. Using async callbacks in `useEffect()` isn’t an option, as the method expects the return value, if present, to be a cleanup callback. With that said, you can still use async in `useEffect()` callback as follows:

```javascript
const useAsyncStuff = () => {
  const [data, setData] = useState(null);
  const [error, setError] = useState("");
  const [loaded, setLoaded] = useState(false);

  useEffect(() => {
    const loadAsyncStuff = async () => {
      try {
        const response = await fetch(/* ... */);
        const json = await response.json();

        setData(json);
      } catch (error) {
        setError(error);
      } finally {
        setLoaded(true);
      }
    };

    loadAsyncStuff();
  }, []);

  return { data, error, loaded };
};
```

All the async tasks are inside the inner `loadAsyncStuff()` function. This function is called immediately after declaration and saves information about its progress with state properties like `loaded` and `error`. If all async tasks succeed, the resulting data is saved to `data`; otherwise, the error is caught and saved to `error`. In both cases, `loaded` is set to `true` when the async task finishes. Wrapping the logic inside `useEffect()` ensures it only runs once.

Such a React hook handles async code easily and provides all the necessary information to its user about the current state of the process. This way, the component can exactly reflect what’s going on.

## Creating Axios Hooks

Now, let’s apply the above technique to create an Axios hook. First, make sure you’ve got Axios installed in your project.

```
npm install axios
```

Let’s start with sample `useAxiosPost()` hook.

```javascript
const useAxiosPost = (url, payload) => {
  const [data, setData] = useState(null);
  const [error, setError] = useState("");
  const [loaded, setLoaded] = useState(false);

  useEffect(() => {
    (async () => {
      try {
        const response = await axios.post(
          url,
          payload
        );

        setData(response.data);
      } catch (error) {
        setError(error.message);
      } finally {
        setLoaded(true);
      }
    })();
  }, []);

  return { data, error, loaded };
};

```

Depending on your preference, you might also prefer the “traditional” Promise syntax, which can be a bit more compact.

```javascript
const useAxiosPost = (url, payload) => {
  const [data, setData] = useState(null);
  const [error, setError] = useState("");
  const [loaded, setLoaded] = useState(false);

  useEffect(() => {
    axios
      .post(url, payload)
      .then((response) => setData(response.data))
      .catch((error) => setError(error.message))
      .finally(() => setLoaded(true));
  }, []);

  return { data, error, loaded };
};
```

The hook uses Axios’s `post()` shorthand method to send a `POST` request to the provided URL with given data. Aside from that, JSON-parsed response data is stored in `data`, and the error message is stored in `error`. The hook can be used as follow:

```javascript
const App = () => {
  const { data, error, loaded } = useAxiosPost(
    "https://httpbin.org/post",
    {
      message: "Hello World",
    }
  );
  const stringifiedData = useMemo(() => {
    return JSON.stringify(data || {});
  }, [data]);

  if (loaded) {
    return error ? (
      <span>Error: {error}</span>
    ) : (
      <p>{stringifiedData}</p>
    );
  }
  return <span>Loading...</span>;
};
```

As you can see, the hook provides all necessary data to render the component throughout the fetching process.

## Improving the hook

With a strong foundation, you can now improve the hook’s functionality. Let’s change the `post()` method to `request()`, passing a complete request configuration including the request method. Additionally, return a `cancel` function that allows users to cancel the request.

```javascript
const useAxios = (url, method, payload) => {
  const [data, setData] = useState(null);
  const [error, setError] = useState("");
  const [loaded, setLoaded] = useState(false);
  const controllerRef = useRef(new AbortController());
  const cancel = () => {
    controllerRef.current.abort();
  };

  useEffect(() => {
    (async () => {
      try {
        const response = await axios.request({
          data: payload,
          signal: controllerRef.current.signal,
          method,
          url,
        });

        setData(response.data);
      } catch (error) {
        setError(error.message);
      } finally {
        setLoaded(true);
      }
    })();
  }, []);

  return { cancel, data, error, loaded };
};
```

[From](https://axios-http.com/docs/cancellation) [*v0.22.0*](https://axios-http.com/docs/cancellation) [onwards](https://axios-http.com/docs/cancellation), Axios supports `AbortController`, making request canceling easier and deprecating its custom `CancelToken` API. To cancel a request with Axios, you first have to create a new `AbortController` instance. [This controller](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) allows aborting one or more HTTP requests. When used in React hook, it has to be wrapped in a `useRef` or similar, not to create a new instance on every re-render.

The most important property of `AbortController` is `signal`, which holds an instance of `AbortSignal` and should be provided to the request(s) the controller is meant for.
With the signal provided in Axios request config, canceling the request is a matter of calling the `abort()` method on the controller instance.


## Open Source Session Replay
_Debugging a web application in production may be challenging and time-consuming. [OpenReplay](https://github.com/openreplay/openreplay) is an Open-source alternative to FullStory, LogRocket and Hotjar. It allows you to monitor and replay everything your users do and shows how your app behaves for every issue. It’s like having your browser’s inspector open while looking over your user’s shoulder. OpenReplay is the only open-source alternative currently available._

![replayer.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1647097041609/Qi6sMOaaB.png)

_Happy debugging, for modern frontend teams - [Start monitoring your web app for free](https://github.com/openreplay/openreplay)._



## Extending integration with React Context

While the hook is already great for all kinds of requests, you can still get it further by combining it with [React Context](https://reactjs.org/docs/context.html).

Axios provides functionality to [create separate instances](https://axios-http.com/docs/instance) of the library with different configurations. It allows you to use custom configs defining, e.g., base URL or default headers to be supplied with every request made through a given instance. It’s handy in larger apps when making many requests to the same origin or connecting with various APIs to reuse configuration.

On top of that, Axios allows you to [define interceptor functions](https://axios-http.com/docs/interceptors) for both the base and custom instances. These allow you to intercept the data before, e.g., the request is sent, or `then()` callback is called. As such, there are both request and response interceptors.

To integrate Axios instances with React, you can use React Context. It’d make the instance available to all child components, from where `useAxios()` hooks will use it to handle the request. If no instance is available, the hook can always fall back to the default one available under `axios`.
To implement this, start by creating a new context provider component:

```javascript
const AxiosContext = createContext(null);
const AxiosInstanceProvider = ({
  config = {},
  requestInterceptors = [],
  responseInterceptors = [],
  children,
}) => {
  const instanceRef = useRef(axios.create(config));

  useEffect(() => {
    requestInterceptors.forEach((interceptor) => {
      instanceRef.current.interceptors.request.use(
        interceptor
      );
    });
    responseInterceptors.forEach((interceptor) => {
      instanceRef.current.interceptors.response.use(
        interceptor
      );
    });
  }, []);

  return (
    <AxiosContext.Provider value={instanceRef.current}>
      {children}
    </AxiosContext.Provider>
  );
};
```

Inside the `AxiosInstanceProvider` component, the Axios instance is created using provided config and saved to `instanceRef` ref. All interceptors are registered inside the `useEffect()` callback to prevent unnecessary processing on re-renders. With this done, return to `useAxios()` to adjust the hook to use the context-provided Axios instance when available.

```javascript
const useAxios = (url, method, payload) => {
  const [data, setData] = useState(null);
  const [error, setError] = useState("");
  const [loaded, setLoaded] = useState(false);
  const contextInstance = useContext(AxiosContext);
  const instance = useMemo(() => {
    return contextInstance || axios;
  }, [contextInstance]);
  const controllerRef = useRef(new AbortController());
  const cancel = () => {
    controllerRef.current.abort();
  };

  useEffect(() => {
    (async () => {
      try {
        const response = await instance.request({
          signal: controllerRef.current.signal,
          data: payload,
          method,
          url,
        });

        setData(response.data);
      } catch (error) {
        setError(error.message);
      } finally {
        setLoaded(true);
      }
    })();
  }, []);

  return { cancel, data, error, loaded };
};
```

The context is loaded using `useContext()` hook to the `contextInstance` variable. The actual `instance` results from `useMemo()` computation and can return the default Axios instance if it’s not provided in the context. To use such a setup, make sure your app is wrapped inside an `AxiosInstanceProvider` component, at least a level higher than where you’ll use `useAxios()` Hook. You can provide config and different interceptors as component props.

```javascript
const App = () => {
  return (
    <AxiosInstanceProvider
      config={{ baseURL: "https://httpbin.org/" }}
    >
      <Test>Test</Test>
    </AxiosInstanceProvider>
  );
};
```

Then, inside the child component, simply use the Hook like before. The use of context, sending, and processing the request will all happen in the background.

```javascript
const Test = () => {
  const { data, error, loaded } = useAxios(
    "/post",
    "POST",
    {
      message: "Hello World",
    }
  );
  const stringifiedData = useMemo(() => {
    return JSON.stringify(data || {});
  }, [data]);

  if (loaded) {
    return error ? (
      <span>Error: {error}</span>
    ) : (
      <p>{stringifiedData}</p>
    );
  }
  return <span>Loading...</span>;
};
```


## Bottom line

Axios is an excellent library if you want consistent data-fetching experience across your front end and Node.js back end. For an HTTP client, it has many features and shortcuts to achieve the thing you want in the best and most pleasing way.

Combining Axios with React can result in great abstraction on top of an asynchronous task. Thanks to React Hooks, the ergonomics and ease of use of such a setup are simply outstanding. If you want to use those two in your next project, you’ve just learned how.