Using axios inside custom react hook

Viewed 3080

I want to create a global axios instance with an interceptor which adds a header with access token. The access token is stored in a cookie, but I can't figure out how to access the cookie inside the interceptor as it is not a react component.

I have tried creating the axios instance in a custom hook like this:

import React, { useEffect, useState } from "react";
import { useCookies } from "react-cookie";
import axios from "axios";

function useApiClient() {
  const [cookies, setCookie] = useCookies(["token"]);
  const [client, setClient] = useState(null);

  useEffect(() => {
    setClient(
      axios
        .create({
          baseURL: "http://localhost:8080/api/",
          responseType: "json",
        })
        .interceptors.request.use(
          (config) => {
            console.log("setting up");
            if (cookies["token"] != null) {
              config.headers.authorization = cookies["token"];
            }
          },
          (error) => Promise.reject(error)
        )
    );
  }, [client]);

  return client;
}

export default useApiClient;

But when I try to call this using:

const client = useApiClient();

function attemptLogin() {
    const credentials = {
      username: username,
      password: password,
    };

    client
      .post("/authenticate", JSON.stringify(credentials), {
         headers: {
          "Content-Type": "application/json",
         },
       }).then(....)

I get below error:

TypeError: client.post is not a function

Can anyone help?

2 Answers

The problem is that you are trying to use react-cookie that provides an api for working with cookies in components. It may be fine for some cases, but for the case when you need to check cookies on every request it forces putting an axios instance somewhere at the top of components hierarchy and also makes it harder to use the instance out of components (most likely you'll pass the instance to outer functions).

What I suggest is to make an instance in a separate module, and use a different tool for getting cookies like universal-cookie.

// apiClient.js
import axios from "axios";
import Cookies from "universal-cookie";

const cookies = new Cookies();

export const apiClient = axios
  .create({
    baseURL: "http://localhost:8080/api/",
    responseType: "json"
  })
  .interceptors.request.use(
    config => {
      console.log("setting up");
      const token = cookies.get("token");
      if (token != null) {
        config.headers.authorization = token;
      }
    },
    error => Promise.reject(error)
  );

After that, you can use this instance everywhere in the app

import React from 'react';
import { apiClient } from "./apiClient";

export default ({ name }) => {
  useEffect(() => {
    apiClient.get(/*...*/).then(/*...*/);
  });

  return <h1>Hello {name}!</h1>;
}

If you prefer to stick to hooks you could wrap a client into one easily:

// apiClient.js
export function useApiClient() { return apiClient; } 

Playgroung with full example

That is maybe because useEffect in useApiClient.js is async method.

Personally I see no harm in directly assigning headers to axios object. However I see you are using axios.create so I would assume you may be making calls to multiple Service APIs/endpoints and all do not need same headers. Anyways...

Suggestions

  1. Make useApiClient a simple class which returns instance of axios with required headers. If not, I am seriously missing the point of using useEffect here.

Behavior

In each file you file call this and all will have separate instances of axios without polluting the global axios object.

useApiClient.js

    function useApiClient {
        return axios
          .create({
            baseURL: "http://localhost:8080/api/",
            responseType: "json"
        })
        .interceptors.request.use(
          config => {
            console.log("setting up");
            const token = cookies.get("token");
            if (token != null) {
              config.headers.authorization = token;
            }
          },
          error => Promise.reject(error)
        );
      }
    }

    export default useApiClient;
  1. Make a Factory Class which returns axios instances based on the input parameter (to assign different headers based on parameter passed and return the instance). You may also have it as Singleton if feasible.

Hope it helps.

Related