6

I am trying to do a simple Redirect with React Router after my user successfully logs in (inside Login.js), and prevent the user from revisiting the login page (inside index.js).

In Login.js, I have onSubmit={this.handleSubmit} within the login button tag, and handleSubmit(e) function to Redirect. I have tried a few other solutions online, but I think my understanding on the usage of the <Redirect/> component is wrong.

In index.js, I have a conditional that tests if the user is signed in, or not signed in, and (poorly) alerts the user on why they can't visit the desired page. I saw this in a Youtube video, but not sure if it's the best way to get the desired effect.

Currently, when I log in successfully, the alert You can't login if you are logged in! is set off, but I obviously don't want the alert going off right after a successful login, I want the Redirect to trigger first. If I swap the two in the parenthesis, React throws an error.

How do I get the Redirect to trigger right after a successful login, but not send the alert You can't login if you are logged in!?

Login.js Component:

import React, { Component } from 'react';
import fire from '../config/Fire.js';
import { Link, Redirect } from 'react-router-dom';
import PasswordMask from 'react-password-mask';

export default class Login extends Component {
    constructor(props) {
        super(props);
        this.login = this.login.bind(this);
        this.handleChange = this.handleChange.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
        this.signup = this.signup.bind(this);
        this.state = {
          email: '',
          password: ''
        };
      }

    handleChange(e) {
        this.setState({ [e.target.name]: e.target.value });
    }

    handleSubmit(e) {
        e.preventDefault();
        <Redirect to="/ticket-list"/>;
    }

    login(e) {
        e.preventDefault();
        fire.auth().signInWithEmailAndPassword(this.state.email, this.state.password).catch((error) => {
            alert(error);
            });
    }

    signup(e){
        e.preventDefault();
        fire.auth().createUserWithEmailAndPassword(this.state.email, this.state.password).catch((error) => {
            alert(error);
            })
    }

    render() {
        return (
        <div className="m-container">
            <h1>Login</h1>
            <hr/>
            <div className="m-container">
                <form onSubmit={this.submitForm}>
                <div>
                    <label for="exampleInputEmail1">Email address: </label>
                    <br/>
                    <input 
                    value={this.state.email} 
                    onChange={this.handleChange} 
                    type="text" 
                    name="email" 
                    id="exampleInputEmail1" 
                    placeholder="you@email.com" />
                </div>
                <div>
                    <label for="exampleInputPassword1">Password: </label>
                    <br/>
                    {/* Margin issue when showing and hiding password */}
                    <PasswordMask 
                    value={this.state.password} 
                    onChange={this.handleChange} 
                    type="password" 
                    name="password" 
                    id="exampleInputPassword1" 
                    placeholder="**********"
                     />
                </div>
                <br/>
                <button 
                    type="submit" 
                    className="button" 
                    onClick={this.login}
                    onSubmit={this.handleSubmit}>Login</button>
                &nbsp;
                <Link className="button-inv" to="/register">Register</Link>
                </form>
            </div>
        </div>
        );
    }
}

index.js Component:

import React, { Component } from 'react';
import { Route, Switch, Redirect } from 'react-router-dom';

import Home from './Home';
import Technician from './Technician';
import About from './About';
import Register from './Register';
import Login from './Login';
import TicketList from './TicketList';

export default class Routes extends Component {

    render() {
        return (
        <Switch>
            <Route path="/" exact component={Home} />
            <Route path="/technician" exact component={Technician} />
            <Route path="/about" exact component={About} />
            <Route path="/register" exact render={()=>(
                this.props.user ? (alert("You can't register if you are logged in!"), (<Redirect to="/"/>)) : (<Register/>)
            )} />
            <Route path="/login" exact render={()=>(
                this.props.user ? (alert("You can't login if you are logged in!"), (<Redirect to="/ticket-list"/>)) : (<Login/>)
            )} />
            <Route path="/ticket-list" exact render={()=>(
                this.props.user ? (<TicketList/>) : (alert("You must log in to visit this page."), (<Redirect to="/login"/>))
            )} />
        </Switch>
        );
    }
};

App.js:

import React, { Component } from 'react';
import { BrowserRouter } from 'react-router-dom';
import Routes from './routes';
import fire from './config/Fire.js';

// CSS
import './assets/css/App.css';
import './assets/css/Header.css';
import './assets/css/Footer.css';
// Components
import Header from './components/Header';
import Footer from './components/Footer';

class App extends Component {
  constructor(props){
    super(props);
    this.state = {
      user:{},
    }
  }

  //When component is done rendering for the first time
  componentDidMount(){
    this.authListener();
  }

  // If user logs in (if) or out (else) this is called
  authListener() {
    fire.auth().onAuthStateChanged((user) => {
      //console.log(user);
      if (user) {
        this.setState({ user });
      } else {
        this.setState({ user: null });
      }
    });
  }


  render() {
    return (
      <BrowserRouter>
        <div className="wrapper">
          <Header user={this.state.user} />
          <div className="body">
            <Routes user={this.state.user} />
          </div>
          <Footer />
        </div>
      </BrowserRouter>
    );
  }
}

export default App;
douglasrcjames
  • 1,133
  • 3
  • 17
  • 37

5 Answers5

8

To solve your problem you have to create separate components for Login/Register and make alerts and redirects there depends on user. You will need High Order Component named withRouter from react-router lib.

Login container:

class LoginContainer extends Component {
  constructor(props) {
    super(props)

    if (props.user) {
      alert("You can't login if you are logged in!")
      props.history.push('/ticket-list')
    }
  }

  render() {
    return <Login />;
  }
}

export default withRouter(LoginContainer)

And then use it in your Routes like this:

<Route path="/login" render={()=> <LoginContainer user={this.props.user} />} />

The same one for Register or you can just make one and get params like alertMessage and redirectTo and use them instead of hardcoded values.

In addition, I advice you to use auth HoC for your private routes, which is not accessible without authentication.

I'd prefer to use new context API for sharing such entity as user, localization, etc, so here is an example how to make PrivateRoute using React Context API.

App.js

...
export const UserContext = React.createContext();
...
class App extends Component {

    state = {
        user: null
    }

    componentDidMount() {
      this.authListener();
    }

    authListener() {
      fire.auth().onAuthStateChanged(user => {
        if (user) {
          this.setState({ user });
        }
      });
    }

    render() {
       <UserContext.Provider value={this.state}>
           <BrowserRouter>
               // another things Switch etc
               ...
           </BrowserRouter>
       </UserContext.Provider>
    }
}

PrivateRoute.jsx

import React, { Component } from 'react';
import { Route, Redirect } from 'react-router-dom'
import { UserContext } from './App'

const PrivateRoute = ({ component: ComposedComponent, ...rest }) => {

  class Authentication extends Component {

    handleRender = props => {
      if (!this.props.user) {
        return <Redirect to="/login" />
      } else {
        return <ComposedComponent user={this.props.user} {...props} />
      }
    }

    render() {
      return (
        <Route {...rest} render={this.handleRender} />
      );
    }
  }

  return (
    <UserContext.Consumer>
      {
        ({ user }) => <Authentication user={user} />
      }
    </UserContext.Consumer>
  )
};

export default PrivateRoute

And then you can use PrivateRoute instead of Route in case when you don't want to show page without authentication.

import PrivateRoute from './PrivateRoute'

...

// all of the component code
render() {
    ...
    <Switch>
        <PrivateRoute path="/ticket-list" component={<TicketList />} />
    </Switch>
    ...
}

Hope it helps! Good luck!

Denys Kotsur
  • 2,579
  • 2
  • 14
  • 26
  • Okay I haven't done the PrivateRoute part yet, but thank you for the help! I implemented the first part. and once logged in and I try to visit login page, I get the alert, then a proper redirect to `ticket-list` page. However, when the user is successfully authenticated after a submit, how do I redirect to `ticket-list`? – douglasrcjames Aug 19 '18 at 23:13
  • Just make the same. Wrap component with `withRouter` HoC and you can use `this.props.history.push("/ticket-list")` in place where you need inside the component. – Denys Kotsur Aug 19 '18 at 23:15
  • Added `this.props.history.push('/ticket-list');` at the end of the `login()` function, and doesn't leave the login page after authentication. Is there a way to refresh the page? Because that redirect properly. – douglasrcjames Aug 19 '18 at 23:41
  • You should do this after promise is resolved because you're making promise-based asynchronous operation. `fire.auth().signInWithEmailAndPassword(this.state.email, this.state.password).then(() => { this.props.history.push('/ticket-list') });` Don't forget to wrap your Login component with `withRouter` HoC. – Denys Kotsur Aug 19 '18 at 23:46
  • Copy pasted that code to my `login` function in `Login.js`, still didn't redirect, but triggered alert and logs user in. – douglasrcjames Aug 19 '18 at 23:58
  • 1
    Also, in your `LoginContainer` code, did you mean to put `` instead of just Login? – douglasrcjames Aug 19 '18 at 23:59
  • @DangerDoug yes, it was just for example. – Denys Kotsur Aug 20 '18 at 00:01
  • @DangerDoug can you share `` component? If you're using something like `PrivateRoute` which I've shared with you or cheking for user, you have to update `user` in you `App` state to let components reflect changes, otherwise your `TicketList` component actually think that `user` is empty, because it's not being updated after login. – Denys Kotsur Aug 20 '18 at 00:06
  • Ahhh I see, I switched `ticket-list` with `about`, and after a successful login, it redirects as intended, thanks for being patient! – douglasrcjames Aug 20 '18 at 00:08
  • @DangerDoug you are welcome! Good luck and have fun ;) – Denys Kotsur Aug 20 '18 at 00:10
3

as the official documentation says, you should create a Private router

this is an example of how I am implementing it using local storage to see if the user data is there or not then redirecting back to the login page if the data is not there. this can be reused in all of your components!

import { BrowserRouter as Router, Route, Redirect, Switch } from 'react-router-dom';

import Login from './views/login';
import Supportedmodels from './views/dashboard/supported-models';


const PrivateRoute = ({ component: Component, ...rest }) => 
(  
  <Route {...rest} render={props => 
  (
    localStorage.getItem('user') ? <Component {...props} /> : <Redirect to={{pathname: '/login'}}/>
  )}/>
);

function App() 
{
  return (

    <Router>
      <Switch>
        <PrivateRoute path="/" component={Supportedmodels} exact />

        <Route path="/login" component={Login} />
      </Switch>      
    </Router>
  );
}

to read more here is the link https://reacttraining.com/react-router/web/example/auth-workflow

jerryurenaa
  • 3,863
  • 1
  • 27
  • 17
  • Hi @jerryurenaa When you say user data, is it the token or just user details? 1. if it is token, how do we check whether it is expired and is still allowed to navigate to next page? 2. if it's user details only, is it enough to check user's authenticty? Because of these concerns, on other redirections, I've used API responses to decide the redirection (In my example API returns 401- Unauthorized if call fails). I'm just trying to understand the better way here. – tyro Jun 17 '20 at 02:40
  • 1
    Hi usually in the component you will check with the server if the user still logged in or if auth is required. if the server response is negative you will delete the localstorage and ask for credentials again. I will make a video on how to do this with backand and frondend. But basically the logic is on every componeneDidMount you will check the auth with the server to see if the user still logged in. if not you will redirect to logout and clear the storage. In your case, if your server returns 401 unauthorized each time you get this delete the localstorage and ask for credentials again. – jerryurenaa Jun 17 '20 at 16:21
3

If anyone is still looking for another answer, one can do something like this:

import { Redirect } from 'react-router-dom';

  render() {
    let isSuccess, message;
    if (this.props.response.hasOwnProperty('response')) {
      message = this.props.response.response.data.message;
      console.log(this.props);
      isSuccess = this.props.response.response.data.success;      
      if (isSuccess) {
        localStorage.setItem('jwt', JSON.stringify(this.props.response.response.data.token));
      }
    }

    return (
        <div>
            {!isSuccess ? <div>{message}</div> : <Redirect to='/admin/dashboard' />}
      </div>
    );
}

Note: For brevity, I've removed non relevant code. And in this example successful LOGIN api call, returned response body contains { success: true, token: <jwt> } and in failed LOGIN api call, returned response body contains { success: false, message: <message> }

tyro
  • 577
  • 8
  • 17
2

You can use:

<Redirect push to="/somewhere/else"/>
Olusola Omosola
  • 847
  • 9
  • 9
  • Where? In `handleSubmit()` I still get the alert from index.js before it redirects. – douglasrcjames Aug 19 '18 at 20:29
  • You need to split your login.js into two components, you should perform your logic then load the right component, so you redirect before it appears. Currently, it has to render before it redirects, hence you get your alert. – Olusola Omosola Aug 19 '18 at 20:35
  • Okay, confused on what the two components would contain? When you say one does logic then load the right component, by component you mean Login.js again or TicketList.js? – douglasrcjames Aug 19 '18 at 21:03
  • I mean, do not use the alert at the point of routing to login. Route to login successfully, then in the constructor of login, just after super, check if the user is logged in, if yes, then route away. Its a simple way of solving it. I used this method once and it worked. – Olusola Omosola Aug 19 '18 at 21:40
  • Added `if(this.props.user){ ; }` just after super in constructor and doesn't redirect after I load the login page. – douglasrcjames Aug 19 '18 at 22:58
  • Since you are in the constructor, do this: if (props.user) { props .history .push("/ticket-list")} – Olusola Omosola Aug 20 '18 at 08:06
  • Thank you for your help! – douglasrcjames Aug 20 '18 at 18:00
  • @Denys Kotsur solution worked best, but you guys were both on the same track! – douglasrcjames Aug 20 '18 at 18:03
0

import { BrowserRouter as Router, Route, Redirect, Switch } from 'react-router-dom';

import Login from './views/login';
import Supportedmodels from './views/dashboard/supported-models';


const PrivateRoute = ({ component: Component, ...rest }) => 
(  
  <Route {...rest} render={props => 
  (
    localStorage.getItem('user') ? <Component {...props} /> : <Redirect to={{pathname: '/login'}}/>
  )}/>
);

function App() 
{
  return (

    <Router>
      <Switch>
        <PrivateRoute path="/" component={Supportedmodels} exact />

        <Route path="/login" component={Login} />
      </Switch>      
    </Router>
  );
}
  • 2
    Please don't post only code as answer, but also provide an explanation what your code does and how it solves the problem of the question. Answers with an explanation are usually more helpful and of better quality, and are more likely to attract upvotes. – I_love_vegetables Jul 16 '21 at 05:30