I have setup axios to use interceptor which does follows,
- Make API call to an endpoint
- If it fails with
status 401
, make call torefresh-token
- If this refresh call fails with
status 401
redirect user to login page.
Happening issues,
- It does not stops on failing first api call, it makes all apis calls:
/students
,/fees/total
and/courses
. - Result of above, it makes multiple calls to
refresh-token
image attached below - 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,
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;