EN VI

Javascript - axios interceptor issue react redux and node?

2024-03-12 17:30:09
How to Javascript - axios interceptor issue react redux and node

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

Solution:

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,

axiosInstance.interceptors.request.use(
  async (config) => {
    // Retrieve the latest userData from localStorage
    let currentUserData = localStorage.getItem("userData")
      ? JSON.parse(localStorage.getItem("userData"))
      : null;

    // Decode the token to check its expiration
    const accessTokenExpiryDate = jwtDecode(currentUserData?.accessToken);

    // If the token has expired, refresh it
    if (accessTokenExpiryDate.exp * 1000 < new Date().getTime()) {
      const { accessToken, refreshToken } = await getTokens(
        JSON.stringify({ token: currentUserData.refreshToken })
      );

      // Update userData with the new tokens
      currentUserData = {
        ...currentUserData,
        accessToken,
        refreshToken,
      };

      // Save the updated userData to localStorage
      localStorage.setItem("userData", JSON.stringify(currentUserData));

      // Update the Authorization header with the new token
      config.headers["Authorization"] = "Bearer " + accessToken;
    } else {
      // If the token hasn't expired, use the current token
      config.headers["Authorization"] = "Bearer " + currentUserData.accessToken;
    }
    
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);
Answer

Login


Forgot Your Password?

Create Account


Lost your password? Please enter your email address. You will receive a link to create a new password.

Reset Password

Back to login