-
-
Notifications
You must be signed in to change notification settings - Fork 11k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Where is the correct place to put interceptors in React app? All calls get blocked after navigation #2315
Comments
Not an axios expert by any means but I ran into this while searching for my own question in issues and thought I'd share what we're doing. Can you initialize at the entry point, where React is getting bootstrapped? This is where we're doing it: ConfigureErrorHandler(); // has the logic to set up interceptor
ReactDOM.render(
<Provider store={store}>
<MuiThemeProvider theme={ORDERCLOUD_THEME}>
<AlertContainer />
<App />
</MuiThemeProvider>
</Provider>,
document.getElementById('root') as HTMLElement
); |
@crhistianramirez Thanx, but the issue is that I want (if possible) to have the interceptor inside a component, so I will have access to the React state and Context. |
I think a couple of things might be happening. Multiple interceptors can be added to an Axios instance. In this case a new interceptor is added every time the Route component is rendered. This will happen on every route change creating an ever growing list of callbacks. The useEffect hook can help here // Remove the existing interceptor when either the user or refreshAccessToken change.
useEffect(() => {
const authInterceptor = axios.interceptors.request.use(function () {/*...*/});
return () => {
axios.interceptors.request.eject(authInterceptor);
}
}, [user, refreshAccessToken]) I think the Moving the I think the reason console.log is logging undefined is because there are multiple interceptors and an earlier interceptor has returned undefined. |
@JaminFarr I tried removing all the If there were multiple interceptors wouldn't at least one of them return the |
Just looking through the source code to see what Axios actually does. It's similar but different to what I thought. When a request is made it creates a promise.then chain of all the request interceptors, in reverse order, ending with the dispatchRequest function.
So every interceptor must return a config or the config will not get to the dispatchRequest. I missed it out in the code above but the interceptor reject functions are also added to the chain. So interceptor1 can catch interceptor2's errors. Also the response interceptors are added, in normal order, at the end after the dispatchRequest. I also noticed when you eject an interceptor, as I suggested, it just sets that position to null in the interceptor array. I now don't think it's a good idea to keep adding and removing interceptors because there will still be a growing internal array of nulls. axios/lib/core/InterceptorManager.js Line 32 in 2ee3b48
|
You can add the interceptors outside of the Route function, as @crhistianramirez suggested, and set a module variable to the context values. It's a little dirty but will do the job and is similar to what you already have. const axiosUserContext = {};
axios.interceptors.request.use(
async config => {
let accessToken = getAccessToken();
let serverCallUrl = new URL(config.url);
if (serverCallUrl.pathname.includes("/auth")) return config;
if (accessToken && axiosUserContext.user) {
const { exp } = jwtDecode(accessToken);
if (Date.now() > exp * 1000) {
if (typeof axiosUserContext.refreshAccessToken !== "function") {
throw new Error("Access Token cannot be refreshed");
}
await axiosUserContext.refreshAccessToken();
accessToken = getAccessToken();
}
config.headers["authorization"] = `Bearer ${accessToken}`;
config.headers["cache-control"] = `no-cache`;
}
return config;
// or throw new Error('User required')
},
// I don't think this function is required
function(error) {
return Promise.reject(error);
}
);
const Routes = () => {
const { user, refreshAccessToken } = useContext(UserContext);
axiosUserContext.user = user;
axiosUserContext.refreshAccessToken = refreshAccessToken;
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={props =>
user ? (
<HomePage renderProps={props}>
<Component />
</HomePage>
) : (
<Redirect to={"/login"} />
)
}
/>
);
return (
<Router>
<Switch>
<Route exact path={"/"} render={() => <Redirect to={"/aaa"} />} />
<Route
path={"/login"}
render={() => (user ? <Redirect to={"/aaa"} /> : <LogIn />)}
/>
<PrivateRoute path="/aaa" component={Comp1} />
<PrivateRoute path="/bbb" component={Comp2} />
<PrivateRoute path="/ccc" component={Comp3} />
</Switch>
</Router>
);
}; |
@JaminFarr Thanx. I think none of those ways are really elegant. I eventually used response interceptor which had no such problem. This just means I will need more server calls. axios.interceptors.response.use(
async (response) => { return response },
async (error) => {
const originalRequest = error.config
const serverCallUrl = new URL(originalRequest.url)
const status = error.response.status
if (
(status === 401 || status === 403) &&
!originalRequest._retry &&
!serverCallUrl.pathname.includes('/auth')
) {
let token = await refreshAccessToken()
setAccessToken(token)
originalRequest._retry = true
originalRequest.headers.authorization = `Bearer ${token}`
return axios(originalRequest)
}
return Promise.reject(error)
}) I don't close the issue though because I still hope there is a solution that can work with request interceptors inside React component. |
Just to inform you: Registering inside of useEffect will not fire interceptor, for API calls done of child components, before first mount phase. So its very dangerous to place interceptor in this phase. |
@reDiesle thanx, so what is the correct place to mount this interceptor? |
Out of my research I just place it in my function, so before everything is mounted. I have no issues with interceptors register again or using multiple interceptors. function App() { We have not experienced any issues with this. |
I Need Sample project |
Closed due to stale. And personally, I recommend @subinpratap's answer to register interceptors in a separated module (which will be only loaded once), instead of some React components. |
Right now I have
axios.interceptors.request.use
in the Router component. I use it to make sure all requests have an access token. I put it there so I can access the React Context and save user data and user-related functions there.However, I am having a weird bug that when I logout and then login again (without refreshing the page) the interceptor can't make any calls after the login.
and I'm getting this error:
I tried putting it in the topmost component and still getting the same issue that after a redirect from login the calls are blocked again.
This is the entire component.
The navigation inside the inner routes works fine, the only problem is navigating on and out of login page
The text was updated successfully, but these errors were encountered: