I have an axios instance that needs to be configured in order to start making normal API calls. I need to set authorization header and put 1 interceptor that will refresh the JWT token on every 401 request. Because I'm using hooks, I made it into a separate component that I'm afterwards nesting inside my App.tsx.
I'm using one of data routers from react-router-dom that supports "loader" functionality. The problem is that when I refresh the page at route (let's say "/profile") it firstly calls the loader function and only then all useEffect instances. It obviously results in 401 error, even though the user has been set in useAuth() hook by the time Axios Component was rendering.
That guy has been having similar issue like me, but I haven't found suitable answer in the comments
React Router Dom V6 loaders fire when router is created
As he states, I can even remove <RouterProvider/> from my App component return statement and it will still call loader before the useEffect. Even though no component of that router will be rendered afterwards.
Here's the video if it will make you understand the issue better:
https://youtu.be/MoV_924owTU
Any help will do!
AxiosSettings.tsx:
import axios from 'axios';
import useAuth from '../hooks/useAuth';
import {useEffect} from 'react';
const api = axios.create({
baseURL: 'http://127.0.0.1:8000/api/',
headers: {
"Content-Type": "application/json",
},
})
export function AxiosSettings({children}: { children: ReactNode }) {
// Using this only because I need to call useAuth() hook in order to get user
const {user, login} = useAuth()
useEffect(() => {
console.log('effect 1')
if (user) {
api.defaults.headers.common["Authorization"] = `Bearer ${user.accessToken}`
} else {
delete api.defaults.headers.common["Authorization"]
}
}, [user])
useEffect(() => {
console.log('effect 2')
const interceptor = api.interceptors.response.use(...)
return api.interceptors.response.eject(interceptor)
}, []);
return children;
}
export default api
My app component is structured like so:
const router = createBrowserRouter(createRoutesFromElements(
<Route element={<RootLayout/>}>
<Route index element={<Home/>}/>
<Route path="register" element={<Signup/>}/>
<Route path="login" element={<Login/>}/>
<Route path="about"/>
<Route element={<RequireAuth/>}>
<Route path="profile" element={<ProfileLayout/>} loader={ProfileLoader}>
<Route index element={<ProfileIndex/>} loader={PrizesLoader}/>
<Route path="history"/>
<Route path="invitations"/>
</Route>
</Route>
<Route path="admin" element={<RequireAdmin/>}>
<Route index element={<AdminPage/>}/>
<Route path="game/:hash" element={<Game/>}/>
</Route>
</Route>
))
function App() {
const [user, setUser] = useState<User | null>(() => {
return SessionStorageUserService.get();
})
return (
<AuthContext.Provider value={{user, setUser}}>
<AxiosSettings>
<RouterProvider router={router} />
</AxiosSettings>
</AuthContext.Provider>
)
}
The
AxiosSettingscould conditionally render itschildrenonly when the axios effects have run and set the headers and attached the response interceptor.The
RouterProviderwill be conditionally rendered and it and the route the user is on won't be rendered until at least the second render cycle when the axiosapiinstance has been configured.