How do you use react-router-dom to redirect user when token has expired?

Viewed 2241

I have a React application that accesses a Flask API. To access some API routes, the user needs to log in. I am using Axios to do the requests. Then, he receives a token which is stored in the local storage. When this token expires and the user makes another request, I want to redirect him to the login page. However, I don't know how I would do it.

I am treating API request errors with Axios response interceptor. It removes the token from the local storage and then should redirect the user to the login page. Since I am using functional components, I could not find an example that fits well (besides downloading another package called history).

I have tried to use the 'useHistory' hook and Redirect from react-router-dom (with a proper BrowserRouter set up), but it doesn't work.

api.js

import axios from "axios"

import { RemoveAuth } from "./Auth"

export const api = axios.create({
    baseURL: "http://localhost:5000/api/",
    timeout: 15000,
})

// more code

api.interceptors.response.use(null, (error) => {

  if(error.response.status === 401){
    RemoveAuth();
  }

  return error;
});

Auth.js

import { useHistory } from "react-router-dom"

export const RemoveAuth = () => {
  let history = useHistory()
  localStorage.clear();
  history.push('/login')
}

routes.js

import React from "react";

import { BrowserRouter, Switch, Route } from "react-router-dom";

import PrivateRoutes from "./PrivateRoutes";


import Dashboard from "../pages/dashboard";

import Login from "../pages/login";
import Logout from "../pages/logout";

const Routes = () => (
  <BrowserRouter>
    <Switch>
      <PrivateRoutes exact path="/dashboard" component={Dashboard} />
      <PrivateRoutes exact path="/logout" component={Logout} />
      <Route exact path="/login" component={Login} />
    </Switch>
  </BrowserRouter>
);

PrivateRoutes.js

import React from "react";

import { Route, Redirect } from "react-router-dom";

import { AuthLogin } from "../services/Auth";

const PrivateRoutes = ({ component: Component, ...rest }) => (
    <Route
      {...rest}
      render={() => (AuthLogin() ? <Redirect to="/login" /> : <Component />)}
    />
);

export default PrivateRoutes;

Thanks in advance for the help!

1 Answers

The simplest thing to do is to create your own history object. Something like this:

import { createBrowserHistory } from 'history';
const history = createBrowserHistory();
export default history;

Then in your provider pass in your custom history object:

import { Router } from 'react-router-dom'
import history from './utils/history'

ReactDOM.render(
  <Router history={history}>
    <App />
  </Router>
  document.getElementById('root')
);

This allows you to utilize your history in non-component code. Just import your history object into your Auth.js file and use it:

import { history } from './utils/history'

export const RemoveAuth = () => {
  localStorage.clear();
  history.push('/login')
}

As an added bonus, now your history lives in a place that is easily mock-able, so creating testing around it is more straightforward. You can find more information about creating your own custom history object in the docs.

Related