React js history.push is undefined - reactjs

I'm new in React.js and in this I want to create a simple login app, so after the login is success, I want to update state value in Parent component and redirect to other page using history.push. But here I have a problem because push is undefined.
I plan to use the state value of isLoggedIn to hide some element before the user login.
Here is the code in Home.jsx
class Home extends Component {
state = {
isLoggedIn: false,
};
handleIsLoggedIn = (value) => {
this.setState({
isLoggedIn: value,
});
};
render() {
return (
<Router>
<Fragment>
<div className="navigation">
<img src="../../drawable/jdl_logo.png" />
<Link to="/member-list">Member</Link>
<Link to="/override-list">Override</Link>
<Link to="/">Login</Link>
</div>
<Route path="/" exact component={() => <Login onLogin={() => this.handleIsLoggedIn()} />} />
<Route path="/member-list" component={MemberDashboard} />
<Route path="/override-list" component={OverrideDashboard} />
</Fragment>
</Router>
);
}
}
And this is how to handle login in Login.jsx
loginUser = () => {
Axios.post("http://private-6fdd31-intern1.apiary-mock.com/interns/login", this.state.user).then(
(res) => {
if (res.data.role === "admin") {
this.onSuccessLogIn();
this.props.history.push("/member-list");
}
},
(err) => {
console.log(err);
}
);
};
onSuccessLogIn = () => {
this.props.onLogin(true);
};

Login isn't receiving the history prop from Route.
You can either pipe it through via the route props:
<Route
path="/"
exact
component={
routeProps => <Login {...routeProps} onLogin={this.handleIsLoggedIn} />
}
/>
Or you can wrap Login with the withRouter HOC and default export it for useage.

Related

Re render a children of Context Provider react

I have a function App in App.js as below
function App() {
return (
<AuthProvider>
<Layout>
<Routes>
{/* Basic routes access allowed by all */}
<Route exact path={RoutesNames.GLOBAL} element={<MainHomePage />} />
<Route exact path={RoutesNames.LOGIN} element={<Login />} />
<Route exact path={RoutesNames.REGISTER} element={<Register />} />
<Route exact path={RoutesNames.CATALOGUE} element={<Catalogue />} />
{/* Protected routes access allowed by user connected */}
<Route exact path={RoutesNames.REGISTRATION_SUCCESS} element={<ProtectedRoute />}>
<Route exact path={RoutesNames.REGISTRATION_SUCCESS} element={<RegistrationSuccess />} />
</Route>
{/* Errors routes */}
<Route path="*" element={<PageNotFound />} />
</Routes>
</Layout>
</AuthProvider>
)
}
The Layout contain the Header bar and the childrens after.
Now when I login from the login page, I'm redirected to Catalogue Page. And I have my AuthProvider who set the isAuth state to true as below :
import React, { Component, createContext } from 'react'
import VerificationUserAuth from '../../utils/VerificationUserAuth'
const AuthContext = createContext()
class AuthProvider extends Component {
constructor(props) {
super(props)
this.state = {
isAuth: false,
}
}
checkAuth = () => {
const user = new VerificationUserAuth().getUserConnected()
if (user) {
this.setState({ isAuth: true })
}
}
render() {
if (!this.state.isAuth) {
this.checkAuth()
}
return (
<AuthContext.Provider value={{ isAuth: this.state.isAuth }}>
{this.props.children}
</AuthContext.Provider>
)
}
}
const AuthConsumer = AuthContext.Consumer
export { AuthProvider, AuthConsumer }
Now the problem is I check in my header who is envelopped by my AuthConsumer if he's authenticated or not and change the menu consequently. But each time I login, i need to refresh the page for Render the AuthProvider who check the authentification.
Btw there is my Header Bar :
export default function HeaderBar() {
return (
<AuthConsumer>
{({ isAuth }) => (
<header className="mb-5">
{isAuth ? (
<p>Connected</p>
) : (
<Navbar.Brand>
<Link to={RoutesNames.LOGIN}>
<Button className="btn btn-rounded btn-primary mr-2">Connexion</Button>
</Link>
<Link to={RoutesNames.REGISTER}>
<Button className="btn btn-rounded btn-normal">S'inscrire</Button>
</Link>
</Navbar.Brand>
)}
</Container>
</Navbar>
</header>
)}
</AuthConsumer>
)
}
So how can I re render my header after login part ? Thank's !
You need to change the isAuth state in a lifecycle component so that means whenever your app is loaded it will be initiated .
Put this function in a lifecycle method :
checkAuth = () => {
const user = new VerificationUserAuth().getUserConnected()
if (user) {
this.setState({ isAuth: true })
}
}
componentDidMount(){
checkAuth()
}
In a functional component :
checkAuth = () => {
const user = new VerificationUserAuth().getUserConnected()
if (user) {
this.setState({ isAuth: true })
}
}
React.useEffect(()=>{
checkAuth()
} , [])

Correct way to redirect user to different routes based on authentication in ReactJS

In my ReactJS app, routes are configured in below way:
class App extends React.Component{
constructor(props){
super(props);
this.state={
isLoggedin: false
}
}
componentDidMount(){
if(localStorage.getItem('name'))
{
this.setState({
isLoggedin: true
})}
}
render(){
return(
<>
<Switch>
<Route exact path="/" render={()=><Login isLoggedin={this.state.isLoggedin} />} />
<Route exact path="/login" render={<Login isLoggedin={this.state.isLoggedin} />} />
<Route exact path="/home" render={()=><Home isLoggedin={this.state.isLoggedin} />} />
</Switch></>
);
}
}
In Login.js:
class Login extends React.Component{
render(){
if(this.props.isLoggedin) return <Redirect to="/home" />;
return(
<h1>Login here</h1>
);
}
}
In Home.js:
class Home extends React.Component{
render(){
if(!this.props.isLoggedin) return <Redirect to="/login" />;
return(
<h1>Home</h1>
);
}
}
So what this code will do is that when the user visits the /, it would first go to Login component and as soon as isLoggedin is set to true, it would redirect the user to Home.js. Same thing would happen if user is not loggedin and he tries to access /home, he would be redirected to /login. Since I am using local storage, all of this would happen in flash of eye. It is also working just fine.
But I doubt if it is the best method to achieve my goal. I want to know if there is any more advisable method to do this.
Thanks!!
A more advisable method would be to decouple the auth checks from the components and abstract this into custom route components.
PrivateRoute - if the user is authenticated then a regular Route is rendered and the props are passed through, otherwise redirect to the "/login" path for user to authenticate.
const PrivateRoute = ({ isLoggedIn, ...props }) => {
return isLoggedIn ? <Route {...props} /> : <Redirect to="/login" />;
};
AnonymousRoute - Basically the inverse of the private route. If the user is already authenticated then redirect them to the "/home" path, otherwise render a route and pass the route props through.
const AnonymousRoute = ({ isLoggedIn, ...props }) => {
return isLoggedIn ? <Redirect to="/home" /> : <Route {...props} />;
};
From here you render the Login and Home components into their respective custom route components.
<Switch>
<PrivateRoute
isLoggedIn={this.state.isLoggedIn} // *
path="/home"
component={Home}
/>
<AnonymousRoute
isLoggedIn={this.state.isLoggedIn} // *
path={["/login", "/"]}
component={Login}
/>
</Switch>
* NOTE: The isLoggedIn={this.state.isLoggedIn} prop is only required here since the isLoggedIn state resides in the App component. A typical React application would store the auth state in a React Context or in global state like Redux, and thus wouldn't need to be explicitly passed via props, it could be accessed from within the custom route component.
Full sandbox code:
const PrivateRoute = ({ isLoggedIn, ...props }) => {
return isLoggedIn ? <Route {...props} /> : <Redirect to="/login" />;
};
const AnonymousRoute = ({ isLoggedIn, ...props }) => {
return isLoggedIn ? <Redirect to="/home" /> : <Route {...props} />;
};
class Login extends Component {
render() {
return (
<>
<h1>Login here</h1>
<button type="button" onClick={this.props.login}>
Log in
</button>
</>
);
}
}
class Home extends Component {
render() {
return (
<>
<h1>Home</h1>
<button type="button" onClick={this.props.logout}>
Log out
</button>
</>
);
}
}
export default class App extends Component {
state = {
isLoggedIn: false
};
componentDidMount() {
if (localStorage.getItem("isLoggedIn")) {
this.setState({
isLoggedIn: true
});
}
}
componentDidUpdate(prevProps, prevState) {
if (prevState.isLoggedIn !== this.state.isLoggedIn) {
localStorage.setItem("isLoggedIn", JSON.stringify(this.state.isLoggedIn));
}
}
logInHandler = () => this.setState({ isLoggedIn: true });
logOutHandler = () => this.setState({ isLoggedIn: false });
render() {
return (
<div className="App">
<div>Authenticated: {this.state.isLoggedIn ? "Yes" : "No"}</div>
<ul>
<li>
<Link to="/">/</Link>
</li>
<li>
<Link to="/home">home</Link>
</li>
<li>
<Link to="/login">log in</Link>
</li>
</ul>
<Switch>
<PrivateRoute
isLoggedIn={this.state.isLoggedIn}
path="/home"
render={() => <Home logout={this.logOutHandler} />}
/>
<AnonymousRoute
isLoggedIn={this.state.isLoggedIn}
path={["/login", "/"]}
render={() => <Login login={this.logInHandler} />}
/>
</Switch>
</div>
);
}
}

React route - logout cannot redirect to correct page

When I click Logout in the side menu,
It should return to /login, but still remain in the same route /home with content disappearing.
Explanation of a few states in App.js
import React, { Component } from "react";
import { BrowserRouter, Switch, Route } from "react-router-dom";
import { Redirect } from "react-router";
import Login from "./Login";
import Drawer from "./Drawer";
console.warn = console.error = () => {};
class App extends Component {
state = {
isAuthenticated: false,
isAuthenticating: true,
user: null
};
setAuthStatus = (authenticated) => {
this.setState({ isAuthenticated: authenticated });
};
setUser = (user) => {
this.setState({ user: user });
};
handleLogout = async () => {
try {
//Sign Out api runs here
this.setAuthStatus(false);
this.setUser(null);
} catch (error) {
console.log(error);
}
};
async componentDidMount() {
try {
// run api change to auth setAuthStatus
// if api no return error > continue
this.setAuthStatus(true);
let user = { id: 123 };
this.setUser(user);
} catch (error) {
console.log(error);
}
this.setState({ isAuthenticating: false });
}
render() {
const authProps = {
isAuthenticated: this.state.isAuthenticated,
user: this.state.user,
setAuthStatus: this.setAuthStatus,
setUser: this.setUser
};
return (
!this.state.isAuthenticating && (
<React.Fragment>
{this.state.isAuthenticated ? (
<Drawer
props={this.props}
auth={authProps}
handleLogout={this.handleLogout}
/>
) : (
<BrowserRouter>
<Switch>
<Redirect exact from="/" to="/Login" />
<Route
path="/login"
render={(props) => <Login {...props} auth={authProps} />}
/>
</Switch>
</BrowserRouter>
)}
</React.Fragment>
)
);
}
}
export default App;
isAuthenticating: componentDidMount will call api to see if the session continues,
if yes, isAuthenticating equals to true and it can prevent losing user auth in current session when refreshing the page or go to other route.
isAuthenticated:
If !isAuthenticated > must stay in/redirect to Login Page
SandBox:
https://codesandbox.io/s/restless-breeze-p5uih?file=/src/App.js
I works as expected in the sandbox. You hide the drawer and the main content if the user is logged out with the
this.state.isAuthenticated ? (Drawer) : <Login/>
You should seperate the drawer from your main content and merge the routers together. You should only use one router for the app in general. Also remove the redirect:
<React.Fragment>
{this.state.isAuthenticated ? (
<Drawer
props={this.props}
auth={authProps}
handleLogout={this.handleLogout}
/>
) : (
<BrowserRouter>
<main
className={clsx(classes.content, {
[classes.contentShift]: open
})}
>
<Switch>
{Routes.map((route) => {
return (
<Route
path={route.path}
render={(prop) => (
<route.component {...prop} auth={props.auth} />
)}
/>
);
})}
<Route
path="/login"
render={(props) => <Login {...props} auth={authProps} />}
/>
</Switch>
</main>
</BrowserRouter>
)}
</React.Fragment>
To route to the logout, first move the browser router up to index to wrap .
This will let you use the withRouter HigherOrderComponent:
export default withRouter(App)
Now you can access history from the props and move to login:
this.props.history.push("/login")`

Programmatically routing after login in React

I am new in React and I was trying to route the authenticated user to the main page. Unfortunately the user remains on the login page, although he logged in successfully. Here the relevant snippet of main.js which handles the routing part.
render(){
const {user} = this.props
{console.log("Logged in user: " + JSON.stringify(this.props.user.email))}
{console.log("Logged in : " + JSON.stringify(this.props.user.loggedIn))}
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={(props) => (
this.props.user.loggedIn === true
? <Component {...props} />
: <Redirect to={{
pathname: '/login',
}} />
)} />
)
<BrowserRouter>
<main>
<Button icon='log out' floated='right' color='blue' onClick={()=>this.userLogout(user)}/>
<Switch>
<PrivateRoute exact path='/' component={withRouter(StudentsMain)}/>
<Route exact path='/about' component={About}/>
<Route exact path='/login' component={withRouter(Login)}/>
<PrivateRoute exact path='/settings' component={withRouter(Settings)}/>
<PrivateRoute exact path='/assessment/:id' component={Assessment}/>
</Switch>
</main>
</BrowserRouter>
And here the relevant part of the login component...
onValidSubmit = (formData) => {
console.log("logging in")
this.props.logInUser(formData)
this.setState({loggedIn: true})
};
onFormInfo = () => {
this.setState({
showInfo:true
})
}
.....
render(){
if (this.state.loggedIn === true) {
console.log('Redirecting to Main Page')
return <Redirect to='/' />
}
Perhaps somebody can give me a hint, why the user remains on the login page. If I change the url in the browser manually the routing works as expected. Thanks, Arnold
In general, you should lift the state up so that you'll trigger a re-render in the right place. In this example, you can see the difference between triggering the state in different places.
import React, { Component } from 'react';
import { render } from 'react-dom';
class Child extends React.Component {
state = {
loggedIn: false,
}
toggle = () => {
this.setState({ loggedIn: !this.state.loggedIn })
}
render() {
return <p onClick={this.props.onClick || this.toggle}>I am {this.state.loggedIn ? 'in' : 'out'}</p>
}
}
class App extends Component {
state = {
loggedIn: false,
}
toggle = () => {
this.setState({ loggedIn: !this.state.loggedIn })
}
render() {
return (
<React.Fragment>
<Child onClick={this.toggle} />
<Child />
</React.Fragment>
);
}
}
render(<App />, document.getElementById('root'));
Here is a live example.

React route authentication

I am trying to create a server-side auth guard for react route. My flow is like... Whenever a route is hit on the front-end, a backend call is made to check if the user's session is present in the Database (since we store session in the database to keep track of sessions).
Here is my app component code:
export default function App() {
const routeComponents = routes.map(({ path, component }) => <AuthenticatedRoute exact path={path} component={component} props={'exact'} key={path} />);
return (
<div>
{window.location.origin === constant.PRODUCTION_DOMAIN_NAME && <Route
path="/"
render={({ location }) => {
if (typeof window.ga === 'function') {
window.ga('set', 'page', location.pathname + location.search);
window.ga('send', 'pageview');
}
return null;
}}
/>}
<Switch>
<Route path="/login" component={LoginPage} />
{routeComponents}
</Switch>
<ToastContainer autoClose={constant.TOASTER_FACTOR} closeButton={false} hideProgressBar />
</div>
);
}
App.propTypes = {
component: PropTypes.any,
};
I have segregated the authenticated route into a separate class component
like:
export class AuthenticatedRoute extends React.Component {
componentWillMount() {
//I call backend here to check if user is authenticated by session Id in DB
}
render() {
const { component: Component, ...rest } = this.props;
return (
<Route exact {...rest} render={(props) => this.state.isAuthenticated ? <Component {...props} /> : <Redirect to="/login" />} />
);
}
}
AuthenticatedRoute.propTypes = {
component: PropTypes.any,
....
};
const mapStateToProps = (state) => ({
reducer: state.get('myReducer'),
});
export default connect(mapStateToProps, { func })(AuthenticatedRoute);
But I face an issue where the login page is redirected twice. Could someone let me know a better approach for this?

Resources