Redirect after login React.js

Viewed 10024

i've been trying since days to redirect my user after login to the home creating a callback function in my App.js and sending it as props to the login class component throught a loginregisterpage class component, but this doesn't work, can someone have a look on it and tell me what i;m missing out? Thank you my code look like this

App.js

import React from 'react'
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'
import { HomePage } from './Pages/HomePage/HomePage'
import { LoginRegisterPage } from './Pages/LoginRegisterPage/LoginRegisterPage'
import 'bootstrap/dist/css/bootstrap.min.css'

export class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      authenticated: false,
    }
    this.handleSuccess = this.handleSuccess.bind(this);

  }
  handleSuccess = (data) => {
    this.props.history.push("/")
  }

  render() {
    return (
      <Router>
        <Switch>
          <Route exact path="/">
            <HomePage />
          </Route>
          <Route exact path="/login-register">
            <LoginRegisterPage onLoginSuccess={this.handleSuccess} />
        </Switch>
      </Router>
    )
  }
}

LoginRegisterPage class component

class LoginPage extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            username: '',
            password: '',
            accessToken: '',
            authenticated: ''
        };
        this.handleChangeUsername = this.handleChangeUsername.bind(this);
        this.handleChangePassword = this.handleChangePassword.bind(this);

    }

    handleChangeUsername(event) {
        this.setState({
            username: event.target.value
        })
    }

    handleChangePassword(event) {
        this.setState({
            password: event.target.value
        })
    }

    handleClick(event) {
        var apiBaseUrl = "https://myapi.com/auth/"
        const payload = {
            method: "POST",
            headers: {
                'Content-Type': 'application/json; charset=utf-8'
            },
            body: JSON.stringify({
                'username': this.state.username,
                'password': this.state.password
            })
        };
        const { username, password } = this.state;

        if (username && password) {
            fetch(apiBaseUrl + 'login', payload)
                .then((response) => {
                    if (response.status === 200) {
                        alert("Logged In! You'll be redirected on Home")
                        return response.json()
                    } else {
                        return alert("wrong pass")
                    }
                }).then((data) => {
                    this.setState({
                        accessToken: data.accestToken,
                        authenticated: data.authenticated
                    });
                    localStorage.setItem('accessToken', data.accessToken);
                    if (data.authenticated === true) {
                        console.log(this.props)
                        this.props.onLoginSuccess(data)
                    }

                })
                .catch((err) => console.log(err));
        } else {
            alert("Cannot be Empty")
        }
    }

    render() {
        return (
            <div>
                <div className="form">
                    <div>
                        <div className="form-input">
                            <div >
                                <div className="userData">
                                    <span>
                                        <img
                                            src={UserIcon}
                                        />
                                    </span>
                                    <input
                                        autocomplete="off"
                                        type="text"
                                        name="username"
                                        placeholder="Username"
                                        value={this.state.username}
                                        onChange={this.handleChangeUsername}
                                    />
                                </div>
                                <div className="userData">
                                    <span>
                                        <img
                                            src={PasswordIcon}
                                        />
                                    </span>
                                    <input
                                        autocomplete="off"
                                        type="password"
                                        name="password"
                                        placeholder="Password"
                                        value={this.state.password}
                                        onChange={this.handleChangePassword}
                                    />
                                    <p style={(this.state.username && this.state.password) ? { display: 'none' } : { display: 'block' }}> Must fill all the form!</p>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
                <div className="form-footer">
                    <img
                        src={Btn}
                        onClick={(event) => this.handleClick(event)}
                    />
                </div>
            </div>
        );
    }
}

LoginPage class component

class LoginPage extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            username: '',
            password: '',
            accessToken: '',
            authenticated: ''
        };
        this.handleChangeUsername = this.handleChangeUsername.bind(this);
        this.handleChangePassword = this.handleChangePassword.bind(this);

    }

    handleChangeUsername(event) {
        this.setState({
            username: event.target.value
        })
    }

    handleChangePassword(event) {
        this.setState({
            password: event.target.value
        })
    }

    handleClick(event) {
        var apiBaseUrl = "https://movies-app-siit.herokuapp.com/auth/"
        const payload = {
            method: "POST",
            headers: {
                'Content-Type': 'application/json; charset=utf-8'
            },
            body: JSON.stringify({
                'username': this.state.username,
                'password': this.state.password
            })
        };
        const { username, password } = this.state;

        if (username && password) {
            fetch(apiBaseUrl + 'login', payload)
                .then((response) => {
                    if (response.status === 200) {
                        alert("Logged In! You'll be redirected on Home")
                        return response.json()
                    } else {
                        return alert("wrong pass")
                    }
                }).then((data) => {
                    this.setState({
                        accessToken: data.accestToken,
                        authenticated: data.authenticated
                    });
                    localStorage.setItem('accessToken', data.accessToken);
                    if (data.authenticated === true) {
                        console.log(this.props)
                        this.props.onLoginSuccess(data)
                    }

                })
                .catch((err) => console.log(err));
        } else {
            alert("Cannot be Empty")
        }
    }

    render() {
        return (
            <div>
                <div className="form">
                    <div>
                        <div className="form-input">
                            <div >
                                <div className="userData">
                                    <span>
                                        <img
                                            src={UserIcon}
                                        />
                                    </span>
                                    <input
                                        autocomplete="off"
                                        type="text"
                                        name="username"
                                        placeholder="Username"
                                        value={this.state.username}
                                        onChange={this.handleChangeUsername}
                                    />
                                </div>
                                <div className="userData">
                                    <span>
                                        <img
                                            src={PasswordIcon}
                                        />
                                    </span>
                                    <input
                                        autocomplete="off"
                                        type="password"
                                        name="password"
                                        placeholder="Password"
                                        value={this.state.password}
                                        onChange={this.handleChangePassword}
                                    />
                                    <p style={(this.state.username && this.state.password) ? { display: 'none' } : { display: 'block' }}> Must fill all the form!</p>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
                <div className="form-footer">
                    <img
                        src={Btn}
                        onClick={(event) => this.handleClick(event)}
                    />
                </div>
            </div>
        );
    }
}
3 Answers

If you're using React Router you can use the Redirect component:

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

export default function PrivateRoute () {
  if (notLoggedIn()) {
    return <Redirect to="/login"/>;
  }
  // return your component
}

But if you're not inside a render function (i.e. you're in a submit callback) or you want to rewrite browser history, use the useHistory hook (note: hooks work only in function components, not class components)

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


const history = useHistory();

// After your login action you can redirect with this command:
history.push('/otherRoute');

Issue

App is defined outside the Router component so it has no history prop function to call to do any navigation.

Solution

Have the LoginRegisterPage component navigate upon successful authentication. It will need to access the history object of the nearest Router context. Normally this is achieved by consuming passed route props from the Route component.

You can:

#1

Move LoginRegisterPage to be rendered by the component prop of the Route so it receives the route props and thus the history object as a prop.

<Route exact path="/login-register" component={LoginRegisterPage} />

LoginRegisterPage

class LoginPage extends React.Component {
    constructor(props) {
        ...
    }

    ...

    handleClick(event) {
        var apiBaseUrl = "https://myapi.com/auth/"
        const payload = {...};
        const { username, password } = this.state;
        const { history } = this.props; // <-- destructure history from props

        if (username && password) {
            fetch(apiBaseUrl + 'login', payload)
                .then((response) => {
                    ...
                }).then((data) => {
                    this.setState({
                        accessToken: data.accestToken,
                        authenticated: data.authenticated
                    });
                    localStorage.setItem('accessToken', data.accessToken);
                    if (data.authenticated === true) {
                        console.log(this.props)
                        this.props.history.push("/"); // <-- navigate!
                    }

                })
                .catch((err) => console.log(err));
        } else {
            alert("Cannot be Empty")
        }
    }

    render() {
        ...
    }
}

#2

Decorate your LoginRegisterPage with the withRouter Higher Order Component so the route props are injected as props.

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

...

const LoginPageWithRouter = withRouter(LoginPage);

Note

If you prefer to do a redirect then replace any history.push calls with history.replace. push is a normal navigation and pushes on a new path on the history state whereas replace replaces the current history entry in the stack. After the auth redirect you probably don't want users to back navigate back to your login page/route.

Edit

If you need the handleSuccess callback to manage some auth state in App then I think it best to let App manage the authentication state and the LoginPage to still handle navigation. In this case, go with the second solution above so it receives both the handleSuccess callback and the history object.

if (data.authenticated === true) {
  this.props.onLoginSuccess(data); // <-- callback to parent to set state
  this.props.history.replace("/"); // <-- imperative navigation
}

Define your handleSucess function in LoginRegisterPage instead of passing it as a prop and this should work.

Related