'Prevent making further calls on failing refresh token call

I have setup axios to use interceptor which does follows,

  1. Make API call to an endpoint
  2. If it fails with status 401, make call to refresh-token
  3. If this refresh call fails with status 401 redirect user to login page.

Happening issues,

  1. It does not stops on failing first api call, it makes all apis calls: /students, /fees/total and /courses.
  2. Result of above, it makes multiple calls to refresh-token image attached below
  3. If refresh token api fails it also not redirecting to /login; url changes but redirection does not happen. When I refresh the page, only then redirection happens.

I will list files in order,

Dashboard.jsx

It uses custom axios hook useAxios to make three api calls to fetch widget data. Each api call must be authenticated.

import { Box, Stack } from '@mui/material';
import { useAxios } from '../../api/use-axios';
import { NewWidget } from '../../components/widget/NewWidget';
import ApiConfig from '../../api/api-config';

const Dashboard = () => {
  const { response: studentResponse } = useAxios(ApiConfig.STUDENT.GET_STUDENTS);
  const { response: courseResponse } = useAxios(ApiConfig.COURSE.GET_COURSES);
  const { response: feesResponse } = useAxios(ApiConfig.FEES.GET_TOTAL);

  return (
    <Box padding={2} width="100%">
      <Stack direction={'row'} justifyContent="space-between" gap={2} mb={10}>
        <NewWidget type={'student'} counter={studentResponse?.data?.length} />
        <NewWidget type={'course'} counter={courseResponse?.data?.length} />
        <NewWidget type={'earning'} counter={feesResponse?.data} />
      </Stack>
    </Box>
  );
};
export default Dashboard;

use-axios.js

It is a custom axios hook, which attaches interceptor on the response as well as request.

import { useState, useEffect } from 'react';
import axios from 'axios';

import history from '../utils/history';
import refreshToken from './refresh-token';

const Client = axios.create();

Client.defaults.baseURL = 'http://localhost:3000/api/v1';

const getUser = () => {
  const user = localStorage.getItem('user');
  return user ? JSON.parse(user) : null;
};

const updateLocalStorageAccessToken = (accessToken) => {
  const user = getUser();
  user.accessToken = accessToken;
  localStorage.setItem('user', JSON.stringify(user));
};

Client.interceptors.request.use(
  (config) => {
    const user = getUser();
    config.headers.Authorization = user?.accessToken;
    return config;
  },
  (error) =>
    // Do something with request error
    Promise.reject(error)
);

Client.interceptors.response.use(
  (response) => response,
  async (error) => {
    // Reject promise if usual error
    if (error.response.status !== 401) {
      return Promise.reject(error);
    }
    const user = getUser();

    const status = error.response ? error.response.status : null;

    const originalRequest = error.config;

    if (status === 401) {
      refreshToken(user.refreshToken)
        .then((res) => {
          console.log('response', res);
          const { accessToken } = res.data.data;
          Client.defaults.headers.common.Authorization = accessToken;
          // update local storage
          updateLocalStorageAccessToken(accessToken);
          return Client(originalRequest);
        })
        .catch((err) => {
          console.log(err);
          if (err.response.status === 401) {
            localStorage.setItem('user', null);
            history.push('/login');
          }
          return Promise.reject(err);
        });
    }
    return Promise.reject(error);
  }
);

export const useAxios = (axiosParams, isAuto = true) => {
  const [response, setResponse] = useState(undefined);
  const [error, setError] = useState('');
  const [loading, setLoading] = useState(true);

  const fetchData = async (params) => {
    try {
      const result = await Client.request({
        ...params,
        method: params.method || 'GET',
        headers: {
          accept: 'application/json',
        },
      });
      setResponse(result.data);
    } catch (error) {
      setError(error);
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    if (isAuto) fetchData(axiosParams);
  }, [axiosParams, isAuto]); // execute once only

  return { fetch: () => fetchData(axiosParams), response, error, loading };
};

refresh-token.js

import Client from './client';

const refreshToken = (refreshToken) =>
  Client({
    url: '/auth/refresh-token',
    method: 'POST',
    data: {
      refreshToken,
    },
  });

export default refreshToken;

client.js

import axios from 'axios';

const Client = axios.create();

Client.defaults.baseURL = 'http://localhost:3000/api/v1';

export default Client;

Errors,

enter image description here

It is supposed to stop on first api call fail, and it should try to refresh token. If refreshing also fails with status 401, it should go back to login page.

So only two API calls: one actual and one to refresh.

Update:

{
 ...,
 "react-router-dom": "^6.2.2",
}

history.js

import { createBrowserHistory } from 'history';

const history = createBrowserHistory();

export default history;

App.js

import { useContext } from 'react';
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import Home from './pages/home/Home';
import Login from './pages/login/Login';
import Single from './pages/single/Single';
import './style/dark.scss';
import { userInputs } from './formSource';
import { AuthContext } from './context/AuthContext';
import Student from './pages/student/Student';
import Course from './pages/course/Course';
import AddNewCourse from './pages/course/AddNewCourse';
import AddNewStudent from './pages/student/AddNewStudent';
import Fees from './pages/fees/Fees';
import SingleStudent from './pages/student/SingleStudent';
import ThemeProvider from './theme';

import history from './utils/history';

function App() {
  const { currentUser } = useContext(AuthContext);

  const RequireAuth = ({ children }) => (currentUser ? children : <Navigate to="/login" />);

  return (
    <ThemeProvider>
      <BrowserRouter history={history}>
        <Routes>
          <Route path="/">
            <Route
              index
              element={
                <RequireAuth>
                  <Home />
                </RequireAuth>
              }
            />
            <Route path="login" element={<Login />} />
            <Route path="students">
              <Route
                index
                element={
                  <RequireAuth>
                    <Student />
                  </RequireAuth>
                }
              />
              <Route path=":studentId" element={<SingleStudent />} />
              <Route
                path="new"
                element={
                  <RequireAuth>
                    <AddNewStudent inputs={userInputs} />
                  </RequireAuth>
                }
              />
            </Route>
            <Route path="courses">
              <Route
                index
                element={
                  <RequireAuth>
                    <Course />
                  </RequireAuth>
                }
              />
              <Route
                path=":courseId"
                element={
                  <RequireAuth>
                    <Single />
                  </RequireAuth>
                }
              />
              <Route
                path="new"
                element={
                  <RequireAuth>
                    <AddNewCourse inputs={userInputs} title="Add New Course" />
                  </RequireAuth>
                }
              />
            </Route>
            <Route
              path="fees"
              element={
                <RequireAuth>
                  <Fees />
                </RequireAuth>
              }
            />
          </Route>
        </Routes>
      </BrowserRouter>
    </ThemeProvider>
  );
}

export default App;



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source