I am running a React frontend app that checks whether a user is currently logged in on a Rails API. I used componentDidMount to check then set the state accordingly but when the page loads for the first time, and the user is logged in, the required info flashes on the page then the app renders and the state goes back to initial state.
Here is my code:
import React, { Component } from "react";
import { Routes, Route } from "react-router-dom";
import Dashboard from "./Dashboard";
import Home from "./Home";
import Header from "./Header";
import Signin from "./Signin";
import Signup from "./Signup";
import { withRouter } from "../withRouter";
import axios from "axios";
class App extends Component {
constructor(props) {
super(props);
this.state = {
loggedInStatus: "NOT_LOGGED_IN",
user: {},
};
this.handleLogin = this.handleLogin.bind(this);
}
checkLoginStatus() {
axios
.get("http://localhost:3001/logged_in", { withCredentials: true })
.then((response) => {
if (
response.data.logged_in &&
this.state.loggedInStatus === "NOT_LOGGED_IN"
) {
this.setState({
loggedInStatus: "LOGGED_IN",
user: response.data.user,
});
} else {
this.setState({
loggedInStatus: "NOT_LOGGED_IN",
user: {},
});
}
})
.catch((error) => console.log("check login error", error));
}
componentDidMount() {
this.checkLoginStatus();
}
handleLogin(data) {
if (data.logged_in) {
this.setState({
loggedInStatus: "LOGGED_IN",
user: data.user,
});
this.props.navigate("/");
}
}
render() {
return (
<React.StrictMode>
<Header />
<div className="app container mt-3">
<Routes>
<Route path="/" element={<Home user={this.state.user.email} />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route
path="/signin"
element={<Signin handleLogin={this.handleLogin} />}
/>
<Route
path="/signup"
element={<Signup handleLogin={this.handleLogin} />}
/>
</Routes>
</div>
</React.StrictMode>
);
}
}
export default withRouter(App);
Strict mode can't automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following functions: Class component constructor , render , and shouldComponentUpdate methods.
Try to remove strict mode and see if the behavior is the same.
Related
I am using react-router-dom to handle routes. I am making a simple login page where you login, session gets generated and redirects to home page.
I want to have a check on my home page to say if the user doesn't have a valid session, throw them back to the login page ("/"). I have been at this for hours and my problem is, axios is async so I haven't found a way to not load the page till this check is done, and the solution I have made shows the page for a split second while this call to my API is being made.
What is the standard way of doing this? My method is below:
App.js
import React, { Component } from 'react'
import Login from './Login';
import Home from './Home';
import { Route, Redirect } from 'react-router-dom';
import axios from 'axios'
export class App extends Component {
constructor(props) {
super(props)
this.state = {
homeRoute: <Route path="/home" component={Home}></Route>
}
}
getAuthorisedAPI() {
axios.get("http://localhost:8080/validateSession")
.then(response => {
console.log(response.data.Success)
if(!response.data.Success) {
this.setState({
homeRoute: <Route path="/home" component={Home}><Redirect to="/" /></Route>
})
}
}).catch(error => {
console.log(error)
})
}
componentDidMount() {
this.getAuthorisedAPI()
}
render() {
axios.defaults.withCredentials = true
return (
<>
<Route exact path="/" component={Login} />
{
this.state.homeRoute
}
</>
)
}
}
export default App
If there was a way axios could do a non async call so the page load waits for this response then the below would be ideal..
const isValid = true
return (
<>
<Route exact path="/" component={Login} />
<Route path="/home" component={Home}>
{
!isValid && <Redirect to="/" />
}
</Route>
</>
)
Thanks
Since you are on client side, I'd recommend to split your app in two. The <AuthenticatedApp /> and the <UnauthenticatedApp />. You can read more about this on https://kentcdodds.com/blog/authentication-in-react-applications.
The other option is to use async/await.
async getAuthorisedAPI() {
try {
const response = await axios.get("http://localhost:8080/validateSession");
console.log(response.data.Success)
if(!response.data.Success) {
this.setState({
homeRoute: <Route path="/home" component={Home}><Redirect to="/" /></Route>
})
}
} catch(error) {
console.log(error)
}
}
async componentDidMount() {
await this.getAuthorisedAPI()
}
I have a React front-end and Python back-end with user session management using flask-login's HttpOnly Session Cookies. How can I restrict react-router-dom routes based on this type of session management? I've created a ProtectedRoute component:
import { Route, Redirect } from 'react-router-dom';
class ProtectedRoute extends Component {
constructor(props) {
super(props);
this.state = {
authenticated: false,
}
}
render() {
const { component: Component, ...props } = this.props
return (
<Route
{...props}
render={props => (
this.state.authenticated ?
<Component {...props} /> :
<Redirect to='/login' />
)}
/>
)
}
}
export default ProtectedRoute;
Is it possible to set this.setState({authenticated: true}) based on the existing session?
Why not pass authenticated (or isEnabledin my example) as a prop? ProtectedRoute will rerender when its props change. This is what I use in my React applications:
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
const ProtectedRoute = ({isEnabled, ...props}) => {
return (isEnabled) ? <Route {...props} /> : <Redirect to="/login"/>;
};
export default ProtectedRoute;
And then you can just use this like so:
<ProtectedRoute path="/dashboard" isEnabled={isAuthenticated()} />
I know this question is old but I was looking for the same thing. Here's my routes.js file:
import auth from './services/auth'
const PrivateRoute = ({isAuthenticated, ...props}) => {
return (isAuthenticated) ? <Route {...props} /> : <Redirect to="/login"/>;
};
class Routes extends React.Component {
constructor(){
super();
this.state = {
isAuthenticated: false
}
}
componentDidMount(){
auth.get('')
.then( async (response) => {
const status = await response.status
if (status === 200) {
this.setState({isAuthenticated: true})
} else {
this.setState({isAuthenticated: false})
}
})
.catch( async (error) => console.log(error))
}
render() {
return (
<BrowserRouter>
<Switch>
<Route path="/login" exact component={Login} />
<PrivateRoute isAuthenticated={this.state.isAuthenticated} path="/" component={() => "barra"}/>
<PrivateRoute isAuthenticated={this.state.isAuthenticated} path="/home" component={() => "home"}/>
<PrivateRoute isAuthenticated={this.state.isAuthenticated} path="/profile" component={() => "profile"}/>
</Switch>
</BrowserRouter>
)
};
}
And auth import is:
const axios = require('axios');
axios.defaults.withCredentials = true;
const auth = axios.create({
baseURL: "http://localhost:5000/auth"
})
export default auth;
So, basically I have a Flask app with Flask-Login running on another local server (with CORS enabled THIS IS VERY IMPORTANT) and if 200 is returned, user on react is authenticated.
I am new to react and I am trying to create a secure area. My app's file structure is as follows:
build
node_modules
public
src
AboutScreen
AdminDash
ContactScreen
HomeScreen
LoginScreen
SignUpScreen
WorkRequestScreen
App.js
index.js
routes.js
serviceWorker.js
package.json
package-lock.json
My routing is done in App.js. This file looks like this:
import React, { Component } from 'react';
import { Route, Redirect } from 'react-router-dom';
import Home from './HomeScreen/Home';
import WorkReq from './WorkRequestScreen/WorkReq';
import About from './AboutScreen/About';
import Contact from './ContactScreen/Contact';
import Login from './LoginScreen/Login';
import Signup from './SignUpScreen/Signup';
import adminDash from './AdminDash/AdminDash';
const fakeAuth = {
isAuthenticated: false,
authenticate(cb) {
this.isAuthenticated = true
setTimeout(cb, 100)
},
signout(cb) {
this.isAuthenticated = false
setTimeout(cb, 100)
}
}
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={(props) => (
fakeAuth.isAuthenticated === true
? <Component {...props} />
: <Redirect to={{
pathname: '/login',
state: { from: props.location }
}} />
)} />
)
class App extends Component {
render() {
return (
<div>
<PrivateRoute path='/adminDash' component={adminDash} />
<Route exact path="/workReq" render={(props) => <WorkReq {...props}/>} />
<Route exact path="/about" component={About}/>
<Route exact path="/contact" component={Contact}/>
<Route exact path="/login" component={Login}/>
<Route exact path="/signup" component={Signup}/>
<Route exact path="/" component={Home}/>
</div>
);
}
}
export default App;
My Login component looks like this:
import App from '../App';
class LoginScreen extends Component {
super(props);
this.state = {
redirectToReferrer: false,
};
login = () => {
App.fakeAuth.authenticate(() => {
this.setState(() => ({
redirectToReferrer: true
}))
})
}
render() {
const { from } = this.props.location.state || { from: { pathname: '/' } }
const { redirectToReferrer } = this.state
if (redirectToReferrer === true) {
return <Redirect to={from} />
}
return (
<div className='buttonCont'>
<button size='lg' type='button' onClick={this.login.bind(this)}>HIT IT!</button>
</div>
);
}
}
Currently this does not redirect the user to another page but will redirect them to AdminDev. The error I am getting is:
TypeError: Cannot read property 'authenticate' of undefined (login.js)
The prolem line is: App.fakeAuth.authenticate(() => {. The thing I would like to know is am I doing this right or am I missing the idea completly?
I have been following these two articles:
https://tylermcginnis.com/react-router-protected-routes-authentication/
https://auth0.com/blog/react-router-4-practical-tutorial/
You are getting this error beacause fakeAuth is not exported from App.js file. Put the fakeAuth object into a separate file i.e. auth.js and export it from there.
// auth.js
const fakeAuth = {
isAuthenticated: false,
authenticate(cb) {
this.isAuthenticated = true
setTimeout(cb, 100)
},
signout(cb) {
this.isAuthenticated = false
setTimeout(cb, 100)
}
}
export { fakeAuth }
Then, import it in App.js and Login.js component like this.
import { fakeAuth } from './path-to-auth-js-file';
I built a HOC to use on protected routes in my app. It takes in the component that should be rendered at the route, checks if the user is authenticated, and then renders that component if they are. It works, but it causes the component to mount/unmount several times (as many times as the render function in my app.js file is called).
routes from my app.js
<Switch>
<Route path='/groups/show/:id'
component={ RequireAuth(Group) } />
<Route path='/groups/create'
component={ RequireAuth(CreateGroup) } />
<Route path='/groups'
component={ RequireAuth(GroupsMenu) } />
<Route path='/tutorials/:id' component={ Tutorial } />
<Route path='/tutorials' component={ TutorialMenu } />
<Route path='/ranked' component={ RankedPlay } />
<Route path='/casual' component={ CasualPlay } />
<Route path='/offline' component={ OfflinePlay } />
<Route path='/signup' component={ Signup } />
<Route path='/' component={ Menu } />
</Switch>
require_auth.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import { store } from '../../index';
import { AUTH_ERROR } from '../../actions';
import PropTypes from 'prop-types';
import Display from './display';
export default function(ComposedComponent) {
class Authentication extends Component {
static propTypes = {
history: PropTypes.object.isRequired
};
componentWillMount() {
const { history } = this.props;
const error = 'You must be logged in to do this. Please login';
if (!this.props.authenticated) {
store.dispatch({ type: AUTH_ERROR, payload: error });
history.push('/');
}
}
componentWillUpdate(nextProps) {
const { history } = this.props;
const error = 'You must be logged in to do this. Please login';
if (!nextProps.authenticated) {
store.dispatch({ type: AUTH_ERROR, payload: error });
history.push('/');
}
}
render() {
return (
<Display if={ this.props.authenticated } >
<ComposedComponent { ...this.props } />
</Display>
);
}
}
function mapStateToProps(state) {
return {
authenticated: state.auth.authenticated
};
}
return withRouter(connect(mapStateToProps)(Authentication));
}
If you remove RequireAuth() from any of the routes, the component only mounts once when you hit the route. But adding it causes the component to mount every time app.js render() fires. Is there a way I can set this up so the component only mounts once?
By calling RequireAuth(Component) in render, you are decorating Component with your HOC in every render call, making that each render returns a new Component each render.
You should decorate Group, CreateGroup and GroupsMenu with RequireAuth, before exporting them. Just as you would with react-redux's connect.
I'm trying to trigger a redirect if a user is logged in. A successful login triggers an update of this.state.user so I'd like to handle the redirect in componentDidUpdate() or another lifecycle method.
The if statement is getting called when I intend for it to, but the redirect does nothing. Any idea as to how I can fix this? I just want this to update the url so it doesn't necessarily need to use Redirect.
I'm not using user authentication currently and don't intend to add it yet.
import React, { Component } from "react";
import "./App.css";
import { BrowserRouter as Router, Route, Redirect } from "react-router-dom";
import AuthContainer from "./components/AuthContainer";
import ChatSelector from "./components/ChatSelector";
import { debug } from "util";
// import ChatRoomContainer from './components/ChatRoomContainer';
class App extends Component {
constructor(props) {
super(props);
this.state = {
user: {}
};
}
setUser = user => {
console.log("setting user");
this.setState({ user });
};
componentDidUpdate() {
// if a user is logged in, redirect them to chat-selector
if (Object.keys(this.state.user).length > 0) {
console.log(this.state.user);
<Router>
<Redirect to="/chat-selector" />;
</Router>;
}
}
render() {
return (
<Router>
<div>
<Route
exact
path="/"
render={props => (
<AuthContainer {...props} setUser={this.setUser} />
)}
/>
<Route
exact
path="/chat-selector"
render={props => <ChatSelector {...props} user={this.state.user} />}
/>
{/* <Route exact path='/chatroom' component={ChatRoomContainer}/> */}
</div>
</Router>
);
}
}
export default App;
I solved this by placing the if statement within render, and adding a redirect boolean to state.
import React, { Component } from "react";
import "./App.css";
import {
BrowserRouter as Router,
Route,
Redirect,
withRouter
} from "react-router-dom";
import AuthContainer from "./components/AuthContainer";
import ChatSelector from "./components/ChatSelector";
import { debug } from "util";
// import ChatRoomContainer from './components/ChatRoomContainer';
class App extends Component {
constructor(props) {
super(props);
this.state = {
user: {},
redirect: false
};
}
setUser = user => {
console.log("setting user");
this.setState({ user });
};
redirect = () => {
this.setState({ redirect: true });
};
render() {
if (
Object.keys(this.state.user).length > 0 &&
this.state.redirect === true
) {
this.setState({ redirect: false });
console.log("logged in");
return (
<Router>
<Redirect to="/chat-selector" />
</Router>
);
} else {
console.log("not logged in");
}
return (
<Router>
<div>
<Route
exact
path="/"
render={props => (
<AuthContainer
{...props}
setUser={this.setUser}
redirect={this.redirect}
/>
)}
/>
<Route
exact
path="/chat-selector"
render={props => <ChatSelector {...props} user={this.state.user} />}
/>
{/* <Route exact path='/chatroom' component={ChatRoomContainer}/> */}
</div>
</Router>
);
}
}
export default App;
There is actually a better way of doing this, and I have recently stumbled across a similar situation.
Since the <Redirect /> technique does not work well with helper functions or lifecycle methods, I suggest to instead use this.props.history.push() inside the ComponentDidUpdate() to perform a redirect. Just remember to wrap your component with the withRouter() HOC.
Example code here: http://blog.jamesattard.com/2018/03/fire-action-creator-after-react-state.html