Search code examples
reactjsreact-hooksaxiosinterceptor

How to stop React from finishing render when axios.interceptors.response handles the error?


I am working on a react app and I use tokens and refresh tokens for authentication. Whenever the backend returns a 401, the axios.interceptors.response picks it up and tries to refresh my token. If it succeeds, it will reinitiate the original call with the updated headers. See the code below:

// To avoid infinite loops on 401 responses
let refresh = false;

axios.interceptors.response.use(
  (resp) => resp,
  async (error) => {
    if (error.response.status === 401 && !refresh) {
      refresh = true;

      const response = await axios.post(
        "/api/auth/refresh",
        {},
        { withCredentials: true }
      );

      if (response.status === 200) {
        axios.defaults.headers.common[
          "Authorization"
        ] = `Bearer ${response.data["accessToken"]}`;

        return axios(error.config);
      }
    }
    refresh = false;
    return error.response;
  }
);

This by itself works great, but not in combination with the code below in one of my components:

const [pages, setPages] = useState();
const [error, setError] = useState();

const navigate = useNavigate();

useEffect(() => {
    async function fetchInfo() {
      const response = await getMyPages();
      if (response.status === 200) {
        setPages(response.data);
      }
      else if (response.status === 401) {
        setError(t("error.notAuthorized"));
        navigate(`/login`, { replace: true });
      }
      // Any other error
      else {
        setError(t("error.unexpected"));
      }
    }
    fetchInfo();
  }, [t, navigate]);


// getMyPages function
export async function getMyPages() {
  try {
    const result = await axios.get(`/api/user/mypages`);
    return result;
  } catch (err) {
    return err.response;
  }
}

The problem is that the user is navigated to /login before the new request (with refreshed token) is made and finished. So when the new request finishes, I am not in the original component anymore and I can no longer update the pages state.

Any suggestions on how to handle this?


Solution

  • useEffect(() => {
        let isMounted = true;
        const controller = new AbortController();
        const getMyPages = async () => {
            try {
                const response = await axios.get(`/api/user/mypages`, {
                    signal: controller.signal
                });
                isMounted && setPages(response.data);
            } catch (err) {
                navigate(`/login`, { replace: true });
            }
        }       
        getMyPages();
        return () => {
            isMounted = false;
            controller.abort();
        }
    }, [])