While building a frontend application using React and Vue, I encountered an issue that required me to use Axios interceptors within a custom Axios instance to handle 401 responses by redirecting users to the login page. However, I encountered errors when attempting to use the useNavigate hook in React or the useRouter hook in Vue. The errors indicated that I could not use the router outside of React or Vue components.
React
After some research, I found a solution to the problem in an article by Arian Hamdi, which can be found at https://dev.to/arianhamdi/react-hooks-in-axios-interceptors-3e1h.
However, while this solution works, I didn't find it to be the most elegant one, and I noticed potential bugs mentioned in the comments under the article. Therefore, I continued to research and eventually developed my own solution to the issue.
The approach I found to handle the issue is a simple and straightforward one. Since we can't use hooks outside of the components, we create a router inside the component. Then we save the router in a separate file, which can be imported wherever it is needed.
- We first need to create a
globalRouter.ts
file in the src directory. This file will contain our router.
import { NavigateFunction } from "react-router-dom";
const globalRouter = { navigate: null } as {
navigate: null | NavigateFunction;
};
export default globalRouter;
- Next, we go to the topmost component of our application (in this case,
App.tsx
) and add the code to save the router in our file.
import { Route, Routes, useNavigate } from "react-router-dom";
import globalRouter from "./globalRouter";
function App() {
const navigate = useNavigate();
globalRouter.navigate = navigate;
}
export default App;
- And finally, we use the router inside of axios interceptors
import globalRouter from "../globalRouter";
import axios from "axios";
const customAxios = axios.create();
customAxios.interceptors.response.use(
function (response) {
return response;
},
function (error) {
if (error.response.status == 401 && globalRouter.navigate) {
globalRouter.navigate("/login");
}
return Promise.reject(error);
}
);
export default customAxios;
Note that we added an additional check in the if statement for globalRouter.navigate
. We declared its type as a union, which includes null. Although we set the router's value in the topmost component, TypeScript forces us to make that check to ensure type safety.
Vue
Although useRouter is a convenient hook in Vue.js, it can only be used within Vue components. If you're using axios in your Vue.js application, you may have come across a solution where you import the router instance into your axios definition and call methods on it directly. While this may seem like a viable solution, it can lead to a significant problem with hot reloading.
I discovered this problem when I noticed that hot reloading wasn't working for the components where I was using my custom axios instance.
After some investigation, I realized that the direct usage of the router instance was causing the issue.
Whenever I made changes to my component, hot reloading didn't occur, and an error appeared in the console: [hmr] Failed to reload /src/[Component.vue]. This could be due to syntax errors or importing non-existent modules. (see errors above).
. To learn more about this error, you can refer to the GitHub issue here.
To get around this issue, we can take a similar approach to the one mentioned earlier. We can create a router using the hook inside a component and store it in a separate file, which can be imported wherever needed.
To implement this solution
- We'll first create a
globalRouter.ts
file that will store our router
import { Router } from "vue-router";
const globalRouter = { router: null } as { router: null | Router };
export { globalRouter };
- Then we'll create a router in the topmost component (in this case
App.vue
) and store it in the object insideglobalRouter.ts
file
<script setup lang="ts">
import { useRouter } from "vue-router";
import { globalRouter } from "./router/globalRouter";
const router = useRouter();
globalRouter.router = router;
</script>
- Finally we can import router from
globalRouter.ts
file in our axios instance definition and use it in interceptors
import axios from "axios";
import { globalRouter } from "../router/globalRouter";
const axiosInstance = axios.create();
axiosInstance.interceptors.response.use(
function (response) {
return response;
},
function (error) {
if (error.response.status == 401) {
globalRouter.router?.push("/");
}
return Promise.reject(error);
}
);
export default axiosInstance;
both hot reloading and navigation are now functioning properly.
Top comments (0)