Reputation: 177
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
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 Component
that 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 authenticated
gets 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