I created a custom hook and created an axios instance in it for fetching new tokens whenever the access token expires , initially on the page load the token seems to be fine , but after the timex expires my token is not getting set whats wrong in my code .
This is my useAxiosInstance.js file
import axios from "axios";
import { jwtDecode } from "jwt-decode";
import { useDispatch } from "react-redux";
import { authSuccess } from "../redux/auth/authSlice";
let userData = localStorage.getItem("userData")
? JSON.parse(localStorage.getItem("userData"))
: null;
export const axiosInstance = axios.create({
baseURL: process.env.REACT_APP_API_URL,
headers: {
Authorization: userData?.accessToken
? `Bearer ${userData.accessToken}`
: "",
},
});
const useAxiosInstance = () => {
const getTokens = async (data) => {
try {
const res = await axios.post(
`${process.env.REACT_APP_API_URL}refresh`,
data,
{
headers: {
"Content-Type": "application/json",
},
}
);
return res.data;
} catch (err) {
return err;
}
};
axiosInstance.interceptors.request.use(
async (config) => {
const accessTokenExpiryDate = jwtDecode(userData?.accessToken);
console.log(accessTokenExpiryDate,"ex")
if (accessTokenExpiryDate.exp * 1000 < new Date().getTime()) {
const { accessToken, refreshToken } = await getTokens(
JSON.stringify({ token: userData.refreshToken })
);
userData = {
...userData,
accessToken,
refreshToken,
};
localStorage.setItem("userData", JSON.stringify(userData));
config.headers["Authorization"] = "Bearer " + accessToken;
}
return config;
},
(error) => Promise.reject(error)
);
return userData;
};
export default useAxiosInstance;
This is my app.js file
import { useDispatch, useSelector } from "react-redux";
import "./app.less";
import Header from "./components/header/Header";
import { BrowserRouter } from "react-router-dom";
import { ToastContainer } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import { useEffect } from "react";
import { authSuccess } from "./redux/auth/authSlice";
import AppRoute from "./routes/AppRoute";
import useAxiosInstance from "./utilities/useAxiosInstance"
function App() {
const theme = useSelector((state) => state.theme);
const auth = useSelector((state) => state.auth);
const dispatch = useDispatch();
const userData = useAxiosInstance();
useEffect(() => {
dispatch(authSuccess(userData));
}, [userData, dispatch]);
return (
<BrowserRouter>
<div
className={theme.mode === "dark" ? "dark-mode app" : "light-mode app"}
>
{auth?.data && <Header />}
<ToastContainer />
<AppRoute />
</div>
</BrowserRouter>
);
}
export default App;
This is my auth slice file
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
data: null,
};
const authSlice = createSlice({
name: "auth",
initialState,
reducers: {
authSuccess: (state, action) => {
state.data =action.payload
},
authLogout: (state, action) => {
state.data = null;
},
},
});
export const { authSuccess, authLogout } = authSlice.actions;
export default authSlice.reducer;
this is my user slice file
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import { showToast } from "../../utilities/toast";
import { authLogout } from "../auth/authSlice";
import { axiosInstance } from "../../utilities/useAxiosInstance";
const initialState = {
update: { loading: false, data: null, error: null },
get: { loading: false, data: null, error: null },
delete: { loading: false, data: null, error: null },
};
export const getUser = createAsyncThunk("user/getUser", (payload) => {
const { data, mode } = payload;
return axiosInstance
.get(`/user/${data?._id}`, {
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + data.accessToken,
},
})
.then((res) => res.data)
.catch((error) => {
showToast(error.response.data.message, mode, "error");
throw error.response.data.message;
});
});
export const deleteUser = createAsyncThunk("user/deleteUser", (payload) => {
const { auth, navigate, mode, dispatch } = payload;
return axiosInstance
.delete(`/user/${auth._id}`, {
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + auth.accessToken,
},
})
.then((res) => {
showToast("Deleted Successfully!", mode, "success");
dispatch(authLogout());
localStorage.removeItem("userData");
navigate("/");
})
.catch((err) => {
showToast(err?.response?.data?.message, mode, "error");
throw err.response.data.message;
});
});
export const editUser = createAsyncThunk("user/editUser", (payload) => {
const { form, data, mode } = payload;
return axiosInstance
.put(`/user/${data._id}`, form, {
headers: {
"Content-Type": "multipart/form-data",
// Authorization: "Bearer " + data.accessToken,
},
})
.then((res) => {
showToast("Updated Successfully!", mode, "success");
})
.catch((err) => {
showToast(err?.response?.data?.message, mode, "error");
throw err.response.data.message;
});
});
const userSlice = createSlice({
name: "user",
initialState,
extraReducers: (builder) => {
builder.addCase(getUser.pending, (state, action) => {
state.get.loading = true;
});
builder.addCase(getUser.fulfilled, (state, action) => {
state.get.loading = false;
state.get.data = action.payload;
});
builder.addCase(getUser.rejected, (state, action) => {
state.get.loading = false;
state.get.error = action.payload;
});
builder.addCase(editUser.pending, (state, action) => {
state.update.loading = true;
});
builder.addCase(editUser.fulfilled, (state, action) => {
state.update.loading = false;
state.update.data = action.payload;
});
builder.addCase(editUser.rejected, (state, action) => {
state.update.loading = false;
state.update.error = action.payload;
});
builder.addCase(deleteUser.pending, (state, action) => {
state.delete.loading = true;
});
builder.addCase(deleteUser.fulfilled, (state, action) => {
state.delete.loading = false;
state.delete.data = action.payload;
});
builder.addCase(deleteUser.rejected, (state, action) => {
state.delete.loading = false;
state.delete.error = action.payload;
});
},
});
export default userSlice.reducer;
since i am calling getUser api on page load its calling the custom hook but for edit and delete only till the access token exists its working and rest of the time , my new token is not getting updated
2
Answers
You issue is,the
userData
within the scope of this interceptor will not automatically update when you fetch new tokens because it’s set outside of the interceptor function. This can lead to the old token being used repeatedly, causing the problems you’re seeing.You can modify your interceptor as following,
you are managing and updating the user data (which includes access tokens) in your
useAxiosInstance
hook. Let’s review your code:The issue might be related to the fact that you are returning
userData
directly from the hook, and it might not be updating when tokens are refreshed. TheuserData
is assigned the initial value retrieved from local storage only once when the hook is initialized. Subsequent updates touserData
inside the interceptor might not reflect in the return value.To fix this, you should update the state inside the hook and return that state. Here’s how you can modify your hook:
With this change, the
userData
state will be updated whenever tokens are refreshed, and the updated state will be returned from the hook. This should ensure that your application uses the latest tokens after refresh.