useContext inside axios interceptor

10.6k views Asked by At

I cant figure out why my useContext is not being called in this function:

import { useContext } from "react";
import { MyContext } from "../contexts/MyContext.js";
import axios from "axios";

const baseURL = "...";

const axiosInstance = axios.create({
  baseURL: baseURL,
  timeout: 5000,
.
.
.
});
axiosInstance.interceptors.response.use(
  (response) => response,
  async (error) => {
    const { setUser } = useContext(MyContext);
    console.log("anything after this line is not running!!!!");
    setUser(null)

.
.
. 

My goal is to use an interceptor to check if the token is live and if its not clear the user and do the login. I'm using the same context in my other react components. And its working fine there, its just not running here! any idea whats I'm doing wrong?

4

There are 4 answers

9
Anatol Zakrividoroga On BEST ANSWER

I had the same issue as you. Here is how I solved it:

You can only use useContext inside a functional component which is why you can't execute setUser inside your axios interceptors.

What you can do though is to create a separate file called AxiosErrorHandler:

// AxiosErrorHandler.js

import { useContext, useEffect } from 'react'
import axios from 'axios'

const AxiosErrorHandler = ({ children }) => {
    const { setUser } = useContext(MyContext);

    useEffect(() => {
        const responseInterceptor = axios.interceptors.response.use(response => response, async (error) => {
            setUser(null)
        })

        return () => {
            axios.interceptors.response.eject(responseInterceptor);
        }
    }, [setUser])

    return children
}

export default AxiosErrorHandler

And then add WithAxios after MyContext.Provider to get access to your context like this for example:

// App.js

const App = () => {
    const [user, setUser] = useState(initialState)

    return (
        <MyContext.Provider value={{ setUser }}>
            <AxiosErrorHandler>
                {/* render the rest of your components here  */}
            </AxiosErrorHandler>
        </MyContext.Provider>
    )
}
0
Sir hennihau On

I went with writing an axios provider, that is added below my other providers in my app entry.

Note: A isInitialized state might be needed for network request during app mounts to use the interceptors correctly.


interface AxiosProviderProps {
    children: ReactNode;
}

const AxiosProvider = ({ children }: AxiosProviderProps) => {
    // Hooks here
    const [isInitialized, setIsInitialized] = useState(false);
    const awesomeCallback = useAwesomeCustomHook()

    useEffect(() => {
        const responseInterceptor = axios.interceptors.response.use(
            (response) => {
                console.log("Response logic here");
                awesomeCallback("success");

                return response;
            },
            async (error) => {
                console.log("Error logic here");
                awesomeCallback("error");

                return error;
            },
        );

        setIsInitialized(true);

        return () => {
            axios.interceptors.response.eject(responseInterceptor);
        };
    }, []);

    return isSet ? children : <></>;
};

Afterwards, wrap your application in the provider.

0
Shamsul Arefin On

worked for me. typescript type added.

import axios, { AxiosInstance, AxiosError } from 'axios';
import { NavigateFunction } from 'react-router-dom';

const createAxiosInstance = (navigate:NavigateFunction): AxiosInstance =>{
  const axiosInstance: AxiosInstance = axios.create({
    baseURL: process.env.REACT_API_HOST,
  });
  axiosInstance.interceptors.response.use(
    (response) => response,
    (error: AxiosError) => {
      if (error.response?.status === 401) {
        navigate("/login")
      }
      return Promise.reject(error);
    }
  );
  return axiosInstance;
}

export default createAxiosInstance;

in the calling component, pass useNavigate() as param to initialize an instance

1
Farrokh Rostami Kia On

I don't have any issues catching the errors in this schema. are you catching them in the axios interceptor? here how I modified it:

useMemo(() => {
    axiosInstance.interceptors.response.use(
      (response) => response,
      async (error) => {
        const originalRequest = error.config;

        // Prevent infinite loops
        if (
          error.response.status === 401 &&
          originalRequest.url === // your auth url ***
        ) {
          handleLogout();
          return Promise.reject(error);
        }

        if (
          error.response.status === 401 &&
          error.response.data.detail === "Token is invalid or expired"
        ) {
          handleLogout(); // a function to handle logout (house keeping ... ) 
          return Promise.reject(error);
        }
        if (
          error.response.data.code === "token_not_valid" &&
          error.response.status === 401 &&
          error.response.statusText === "Unauthorized"
        ) {
          const refreshToken = // get the refresh token from where you store

          if (refreshToken && refreshToken !== "undefined") {
            const tokenParts = JSON.parse(atob(refreshToken.split(".")[1]));

            // exp date in token is expressed in seconds, while now() returns milliseconds:
            const now = Math.ceil(Date.now() / 1000);

            if (tokenParts.exp > now) {
              try {
                const response = await axiosInstance.post(
                  "***your auth url****",
                  {
//your refresh parameters
                    refresh: refreshToken,
                  }
                );
                
// some internal stuff here ***

                return axiosInstance(originalRequest);
              } catch (err) {
                console.log(err);
                handleLogout();
              }
            } else {
              console.log("Refresh token is expired", tokenParts.exp, now);
              handleLogout();
            }
          } else {
            console.log("Refresh token not available.");
            handleLogout();
          }
        }

        // specific error handling done elsewhere
        return Promise.reject(error);
      }
    );
  }, [setUser]);