Reputation: 1187
My problem is simple, at least it seems. I have state in my redux store that's holding the state of whether the user is logged in or not. That is all working fine, but when the user refreshes the page, for a moment while the authenticated state is async getting it's data, the render runs and state is undefined.
Since, state is undefined, my redirect to /login runs, so the refresh kicks me out of the app and back to login, which then checks to see if I'm already logged in and take me to the homepage.
Any ideas on how to resolve this:
{
!this.props.authenticated && (
<Switch>
<Route path="/login" component={LoginForm} />
<Route path="/register" component={RegisterForm} />
<Route path="" render={props => {
return <Redirect to="/login" />
}}
/>
</Switch>
)
}
So, when this.props.authenticated is false for that short period of time, it hits the login redirect. But, a few ms later, this.props.authenticated is true and since the user is already logged in, is redirected to the home route.
Any ideas?
Upvotes: 2
Views: 10977
Reputation: 2176
First of all when user try to login then you receive token in response when user is authenticated. now you have to store token in localStorage using
if(user.token){
localStorage.setItem('user', JSON.stringify(user));
}
it is indicated that when you have token in localstorage you are login otherwise you are logout.
now try to set state to redirect to home page if you want to go home page after login.
this.setState({redirectToReferrer: true});
now return redirect to desire page
if (this.state.redirectToReferrer){
return (<Redirect to={'/home'}/>)
}
login.js
import React from 'react';
import axios from 'axios';
import {Redirect} from 'react-router-dom';
export default class Login extends React.Component{
constructor(props){
super(props);
this.state = {
email : '' ,
password : '',
redirectToReferrer : false
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event){
this.setState({
[event.target.name] : event.target.value
});
}
handleSubmit(event){
event.preventDefault();
const user = {
email : this.state.email,
password : this.state.password
};
if(this.state.email && this.state.password)
{
axios.post(`{Api}/login`,user)
.then((response) =>
{
let userresponse = response;
console.log(userresponse.data);
if(userresponse.token){
sessionStorage.setItem('data',JSON.stringify(userresponse));
this.setState({redirectToReferrer: true});
}
},this)
.catch((error) => alert(error))
}
}
render(){
if (this.state.redirectToReferrer){
return (<Redirect to={'/user'}/>)
}
if (sessionStorage.getItem('data')){
return (<Redirect to={'/user'}/>)
}
return(
<div>
<form ref="formdemo" onSubmit={this.handleSubmit}>
<label>
Username:
<input type="email" name="email" onChange={this.handleChange} placeholder="Enter Your EmailID" required/></label><br/>
<label>
Password :
<input type="password" name="password" onChange={this.handleChange} placeholder="Enter Your Password" required/></label><br/>
<input type="submit"/>
</form>
</div>
)
}
}
Upvotes: 2
Reputation: 3359
You can use react-router-dom for auth work flow.
import React from "react";
import {
BrowserRouter as Router,
Route,
Link,
Redirect,
withRouter
} from "react-router-dom";
////////////////////////////////////////////////////////////
// 1. Click the public page
// 2. Click the protected page
// 3. Log in
// 4. Click the back button, note the URL each time
const AuthExample = () => (
<Router>
<div>
<AuthButton />
<ul>
<li>
<Link to="/public">Public Page</Link>
</li>
<li>
<Link to="/protected">Protected Page</Link>
</li>
</ul>
<Route path="/public" component={Public} />
<Route path="/login" component={Login} />
<PrivateRoute path="/protected" component={Protected} />
</div>
</Router>
);
const fakeAuth = {
isAuthenticated: false,
authenticate(cb) {
this.isAuthenticated = true;
setTimeout(cb, 100); // fake async
},
signout(cb) {
this.isAuthenticated = false;
setTimeout(cb, 100);
}
};
const AuthButton = withRouter(
({ history }) =>
fakeAuth.isAuthenticated ? (
<p>
Welcome!{" "}
<button
onClick={() => {
fakeAuth.signout(() => history.push("/"));
}}
>
Sign out
</button>
</p>
) : (
<p>You are not logged in.</p>
)
);
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={props =>
fakeAuth.isAuthenticated ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
)
}
/>
);
const Public = () => <h3>Public</h3>;
const Protected = () => <h3>Protected</h3>;
class Login extends React.Component {
state = {
redirectToReferrer: false
};
login = () => {
fakeAuth.authenticate(() => {
this.setState({ redirectToReferrer: true });
});
};
render() {
const { from } = this.props.location.state || { from: { pathname: "/" } };
const { redirectToReferrer } = this.state;
if (redirectToReferrer) {
return <Redirect to={from} />;
}
return (
<div>
<p>You must log in to view the page at {from.pathname}</p>
<button onClick={this.login}>Log in</button>
</div>
);
}
}
export default AuthExample;
refer link https://reacttraining.com/react-router/web/example/auth-workflow
Upvotes: 0
Reputation: 7575
Ideally you wouldn't render your route straight away but wait until your authentication request has resolved and you have a clear state.
Something like this:
class App extends React.Component {
constructor( props ) {
super( props );
this.state = {
// You could just check if authenticated is null,
// but I think having an extra state makes is more clear
isLoading: true,
authenticated: null,
};
this.checkAuthentication();
}
checkAuthentication() {
// Some async stuff checking against server
// I’ll simulate an async call with this setTimeout
setTimeout(
() => this.setState( {
authenticated: Boolean( Math.round( Math.random() ) ),
isLoading: false,
} ),
1000
);
}
render() {
// Render a loading screen when we don't have a clear state yet
if ( this.state.isLoading ) {
return <div>loading</div>;
}
// Otherwise it is safe to render our routes
return (
<div>
routes,<br />
random authenticated:
<strong>
{ this.state.authenticated.toString() }
</strong>
</div>
);
}
}
ReactDOM.render( (
<App />
), document.querySelector( 'main' ) );
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<main></main>
Upvotes: 4
Reputation: 1187
Okay, lumio helped get me on the right track with setTimeout, so instead I worked it out with async/await:
class App extends Component {
state = {
error: "",
isLoading: true,
}
async componentDidMount() {
let token = localStorage.getItem('jwtToken');
if (token) {
setAuthToken(token);
await this.props.isAuthenticatedAction(true);
} else {
await this.props.isAuthenticatedAction(false);
}
this.setState({
isLoading: false,
});
}
handleLogout = (evt) => {
evt.preventDefault();
localStorage.removeItem('jwtToken');
window.location.reload();
}
render() {
if (this.state.isLoading) {
return <div></div>;
} else {
// return my regular content
}
Upvotes: 1