0

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;

confusedWarrior
  • 938
  • 2
  • 14
  • 30
  • 1
    In the interceptor, along with checking status code 401, also check the endpoint for which the status code is 401 if it's for the refresh token endpoint, then instead of calling it again, just redirect the user to the login page, using window.location.href = "Login url" – Charchit Kapoor May 24 '22 at 07:43
  • `window.location.href ` will fetch the contents from server again, the files and other resources. Why `history.push()` not working? – confusedWarrior May 24 '22 at 07:45
  • It depends on which version of react-router you are using, does this link helps in any way. '''https://stackoverflow.com/questions/42701129/how-to-push-to-history-in-react-router-v4''' – Charchit Kapoor May 24 '22 at 07:49
  • I have updated. I am not using v4. Also, added other files. – confusedWarrior May 24 '22 at 07:53
  • For v6, you need to use this I believe useNavigate(). https://stackoverflow.com/questions/63471931/using-history-with-react-router-dom-v6 – Charchit Kapoor May 24 '22 at 07:57
  • You can't use `useNavigate` outside component, right? – confusedWarrior May 24 '22 at 07:58
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/244972/discussion-between-charchit-kapoor-and-confusedwarrior). – Charchit Kapoor May 24 '22 at 08:02

0 Answers0