Slait
Slait

Reputation: 177

Reactjs not redirecting as intended

I am encountering an issue after "logging in" and trying to perform some redirect(s). This issue is after the API response. I'm using CoreUI pro with react router v4.

When redirect in Login component I get this error

index.js:2178 Warning: You tried to redirect to the same route you're currently on: "/authenticated" And stops there and does not proceed to dashboard.

When trying to redirect in parent component (App) "nothing" happens

Login component still sends data to the handleLogin binding in App and handleLogin does what it needs too, but does not Redirect in the child Switch section like below.

Note, comments are things I have tried so far.

Component App (parent)

import React, { Component } from 'react';
import { HashRouter, Route, Switch, Redirect } from 'react-router-dom';
import './App.css';
// Styles
// CoreUI Icons Set
import '@coreui/icons/css/coreui-icons.min.css';
// Import Flag Icons Set
import 'flag-icon-css/css/flag-icon.min.css';
// Import Font Awesome Icons Set
import 'font-awesome/css/font-awesome.min.css';
// Import Simple Line Icons Set
import 'simple-line-icons/css/simple-line-icons.css';
// Import Main styles for this application
import './scss/style.css'

// Containers
import { DefaultLayout } from './containers';
// Pages
import { Login, Page404, Page500, Register } from './views/Pages';

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

componentDidUpdate(prevProps, prevState) {
    console.log(prevProps, prevState);
    console.log(localStorage.getItem('authenticated'));
    return localStorage.getItem('authenticated') === true
}

handleLogin(jwtToken, authResult) {
    console.log(jwtToken, authResult);
    localStorage.setItem('jwtToken', jwtToken);
    localStorage.setItem('authenticated', authResult);
    this.setState({ authenticated: true });
}

render() {
    const ProtectedRoute = ({ component: Component, ...rest }) => (
        <Route {...rest} render={(props) => (
            localStorage.getItem('authenticated') === true
                ? <Component {...props} />
                : () => {
                    console.log('failed to login');
                    return <Redirect  to={{
                        pathname: '/login',
                        state: { from: props.location }
                    }} />
                }
        )} />
    );

    return (
        <HashRouter>
            <Switch>
                <Route exact path="/login" name="Login Page" component={Login} />
                <Route exact path="/register" name="Register Page" component={Register} />
                <Route exact path="/404" name="Page 404" component={Page404} />
                <Route exact path="/500" name="Page 500" component={Page500} />
                <Route path="/" name="Login Page" render={(props) => {
                    return(<Login {...props} handleLogin={this.handleLogin}/>)
                }}/>
                <ProtectedRoute exact path="/authenticated" name="Dashboard" component={DefaultLayout} />

                /*{this.state.authenticated ? <Redirect to='/authenticated' /> : ''} */

            </Switch>
        </HashRouter>
    );
}
}

export default App;

Component Login (Child of App)

import React, { Component } from 'react';
import { Button, Card, CardBody, CardGroup, Col, Container, Input, InputGroup, InputGroupAddon, InputGroupText, Row } from 'reactstrap';
import axios from 'axios';
import { Redirect, Switch } from 'react-router-dom';

//import jwtDecode from 'jwt-decode';
const apiUrl = 'http://localhost:3015';
const emailRegex = 'blah blah blah';
const brandDanger = '#f86c6b';
const errorStyle = {
    marginLeft: '1em',
    marginTop: '-1em',
    color: brandDanger,
    fontStyle: 'italic',
};

class Login extends Component {

constructor(props) {
    super(props);
    // initial state of things
    this.state = {
        email: '',
        password: '',
        isValidEmail: null,
        isValidPassword: null,
        disableLoginButton: true,
        authenticated: false
    };
    this.handleChange = this.handleChange.bind(this);
    this.submitRequest = this.submitRequest.bind(this);
}

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

    if (emailRegex.test(this.state.email) && this.state.password.length > 0) {
        this.setState({
            isValidEmail: true,
            isValidPassword: true,
            disableLoginButton: false
        });
    } else {
        this.setState({
            isValidEmail: false,
            isValidPassword: false,
            disableLoginButton: true
        });
    }
}


async submitRequest() {
    this.setState({disableLoginButton: !this.state.disableLoginButton});
    const login = await axios.post(`${apiUrl}/api/auth/login`, {email: this.state.email, password: this.state.password});
    try {
        if (login.data.authentication) {
            this.props.handleLogin(login.data.token, login.data.authentication);
            this.setState({ authenticated: true });

        }
    } catch (err) {
        console.log(err)
    }
}

render() {
    //console.log(`thisprops = \n${JSON.stringify(this.props)}`);


    if (this.state.authenticated) {
        return <Redirect to='/authenticated'/>
    }

    return (
        <div className="app flex-row align-items-center">
            <Container>
                <Row className="justify-content-center">
                    <Col md="8">
                        <CardGroup>
                            <Card className="p-4">
                                <CardBody>
                                    <h1>Login</h1>
                                    <p className="text-muted">Sign In to your account</p>
                                    <InputGroup className="mb-3">
                                        <InputGroupAddon addonType="prepend">
                                            <InputGroupText>
                                                <i className="icon-user"></i>
                                            </InputGroupText>
                                        </InputGroupAddon>
                                        <Input
                                            type="text"
                                            placeholder="Email"
                                            name="email"
                                            onChange={this.handleChange}
                                        />
                                    </InputGroup>
                                    {this.state.isValidEmail === null ? '' : !this.state.isValidEmail ? <p style={errorStyle}>Email is not valid.</p> : '' }
                                    <InputGroup className="mb-4">
                                        <InputGroupAddon addonType="prepend">
                                            <InputGroupText>
                                                <i className="icon-lock"></i>
                                            </InputGroupText>
                                        </InputGroupAddon>
                                        <Input
                                            type="password"
                                            placeholder="Password"
                                            name="password"
                                            onChange={this.handleChange}
                                        />
                                    </InputGroup>
                                    {this.state.isValidPassword === null ? '' : !this.state.isValidPassword ? <p style={errorStyle}>Password is required.</p> : '' }
                                    <Row>
                                        <Col xs="6">
                                            <Button
                                                color="primary"
                                                className="px-4"
                                                onClick={this.submitRequest}
                                                disabled={this.state.disableLoginButton}
                                            >Login</Button>
                                        </Col>
                                        <Col xs="6" className="text-right">
                                            <Button color="link" className="px-0">Forgot password?</Button>
                                        </Col>
                                    </Row>
                                </CardBody>
                            </Card>
                            <Card className="text-white bg-primary py-5 d-md-down-none" style={{ width: 44 + '%' }}>
                                <CardBody className="text-center">
                                    <div>
                                        <h2>Sign up</h2>
                                        <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut
                                            labore et dolore magna aliqua.</p>
                                        <Button color="primary" className="mt-3" active>Register Now!</Button>
                                    </div>
                                </CardBody>
                            </Card>
                        </CardGroup>
                    </Col>
                </Row>
                <Row className="justify-content-center">
                    <Col md="8">
                        <span className="ml-1">© </span>
                    </Col>
                </Row>
            </Container>
        </div>

    );
}
}

export default Login;

EDIT

Also, after /authenticated, it's supposed to render the dashboard like below. It doesn't get rendered. The console.log does not happen in class DefaultComponent extends Component {...}

Component DefaultLayout (Child of App?)

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

import {
    AppAside,
    AppBreadcrumb,
    AppFooter,
    AppHeader,
    AppSidebar,
    AppSidebarFooter,
    AppSidebarForm,
    AppSidebarHeader,
    AppSidebarMinimizer,
    AppSidebarNav,
} from '@coreui/react';
// sidebar nav config
import navigation from '../../_nav';
// routes config
import routes from '../../routes';

import DefaultAside from './DefaultAside';
import DefaultFooter from './DefaultFooter';
import DefaultHeader from './DefaultHeader';

class DefaultLayout extends Component {
render() {
    //console.log(localStorage.getItem('jwtToken'));
    console.log(`default layout props:\n ${this.props}`);
    return (
        <div className="app">
            <AppHeader fixed>
                <DefaultHeader />
            </AppHeader>
            <div className="app-body">
                <AppSidebar fixed display="lg">
                    <AppSidebarHeader />
                    <AppSidebarForm />
                    <AppSidebarNav navConfig={navigation} {...this.props} />
                    <AppSidebarFooter />
                    <AppSidebarMinimizer />
                </AppSidebar>
                <main className="main">
                    <AppBreadcrumb appRoutes={routes}/>
                    <Container fluid>
                        <Switch>
                            {routes.map((route, idx) => {
                                    return route.component ? (<Route key={idx} path={route.path} exact={route.exact} name={route.name} render={props => (
                                            <route.component {...props} />
                                        )} />)
                                        : (null);
                                },
                            )}

                            <Redirect exact from='/authenticated' to='/dashboard'/>

                        </Switch>
                    </Container>
                </main>
                <AppAside fixed hidden>
                    <DefaultAside />
                </AppAside>
            </div>
            <AppFooter>
                <DefaultFooter />
            </AppFooter>
        </div>
    );
}
}

export default DefaultLayout;

Upvotes: 0

Views: 965

Answers (1)

Slait
Slait

Reputation: 177

Fixed the issue after a fresh rebuild of my project and performed one of my earlier methods.

Working method of client-side auth flow.

In the parent App Component on request of / react-router responds with the PrivateRoute Componentthat negotiates if user is logged in or not and responds with either requiring/redirecting to login or proceeding to the dashboard.

If login is required, Login Component gets rendered and the state authenticated is set to false. If successful login response from API, state authenticatedgets set to true. This will cause the app to rerender with <Redirect .../>and will again hit the PrivateRoute located in the parent, App Component.

From here, proper authentication in PrivateRoute is negotiated and then proceeds as intended. See below for src.

App Component

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

import ...

class App extends Component {
  render() {
    const checkAuth = () => {
        if (!localStorage.getItem('token') || localStorage.getItem('token') === false) {
            return false;
        }
        else if (jwtDecode(localStorage.getItem('token')).exp < Date.now() / 1000) {
            localStorage.setItem('token', false);
            return false;
        }
        console.log(jwtDecode(localStorage.getItem('token')).exp);
        console.log(Date.now() / 1000);
        console.log(jwtDecode(localStorage.getItem('token')));
        return true;
    };

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

    return (
      <HashRouter>
        <Switch>
          <Route exact path="/login" name="Login Page" component={Login} />
          <Route exact path="/register" name="Register Page" component={Register} />
          <Route exact path="/404" name="Page 404" component={Page404} />
          <Route exact path="/500" name="Page 500" component={Page500} />
          <PrivateRoute path="/" name="Home" component={DefaultLayout} />
        </Switch>
      </HashRouter>
    );
  }
}

export default App;

Login Component

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

import ...

class Login extends Component {
    constructor(props) {
        super(props);
        // initial state of things
        this.state = {

            ...

            authenticated: false // set auth to false. If login request is successful, set to true which will allow the Redirect below to work.

        };
        this.handleChange = this.handleChange.bind(this);
        this.submitRequest = this.submitRequest.bind(this);
    }

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

        if (emailRegex.test(this.state.email) && this.state.password.length > 0) {
            this.setState({
                isValidEmail: true,
                isValidPassword: true,
                disableLoginButton: false
            });
        } else {
            this.setState({
                isValidEmail: false,
                isValidPassword: false,
                disableLoginButton: true
            });
        }
    }


    submitRequest = async() => {
        this.setState({disableLoginButton: !this.state.disableLoginButton});
        const login = await axios.post(`${apiUrl}/api/auth/login`, {email: this.state.email, password: this.state.password});
        try {
            if (login.data.authentication) {
                localStorage.setItem('token', login.data.token);

                // successful login, set state of authenticated to true
                this.setState({ authenticated: !this.state.authenticated });

            }
        } catch (err) {
            console.log(err)
        }
    }

    render() {
        if (this.state.authenticated) {

            return  <Redirect to='/'/> // Request '/' route from react router if auth'd.

        }

        return (
            <div className="app flex-row align-items-center"> 

                 ... Login Form

            </div>

        );
    }
}

export default Login;

DefaultLayout/Dashboard Component

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

import navigation from '../../_nav';
// routes config
import routes from '../../routes';

import ...


class DefaultLayout extends Component {
      render() {
            return (
              <div className="app">
                ...
                <Container fluid>

                      <Switch>
                            {routes.map((route, idx) => {
                                return route.component ? (<Route key={idx} path={route.path} exact={route.exact} name={route.name} render={props => (
                                    <route.component {...props} />
                                      )} />)
                                  : (null);
                                  },
                            )}

                            <Redirect from="/" to="/dashboard" /> // To dashboard!



                      </Switch>
                  ...
                  </div>
      );
   }
}

export default DefaultLayout;

Upvotes: 1

Related Questions