Auth Protected Routes React Router V4 - Passing Props - reactjs

I have created a reactJS app using create-react-app using Flux as an architecture where I want to have some routes accessible without being authenticated and some only accessible while authenticated. Using the flux design pattern I am passing the state of the store down through each component using props so the store state is available for all child components that require it.
I have studied the documentation here (the example is also pasted below) to try and understand how to achieve the above outcome in my app.
I can't see how I can adapt the example to pass state down to the component called within the protected route without doing this with an explicit name like how component is passed.I want to acheive...
Pass the component to PrivateRoute so it can be called if my user is authenticated.
Pass all props from the parent component to the component called by PrivateRoute (this is to allow me to keep cascading the store state down through the props and also to check in the store state if the user is logged in).
I think I am perhaps misunderstanding something fundamental here. Can anyone advise please?
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;
Here is a section from my actual code...
class Page extends React.Component{
// constructor(props) {
// super(props);
// }
render(){
return(
<Router>
<div>
<Route exact path="/" render={()=><HomePage {...this.props}/>}/>
<Route path="/login" render={()=><LoginPage {...this.props}/>}/>
<PrivateRoute exact path="/protected" component={Main} extra="Boo!"/>
</div>
</Router>);
}
}
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={(props) =>
(console.log(this.props.extra) || 1) ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
)
}
/>
);
This.props.extra is undefined.

If you are looking for a way to pass extra props to the PrivateRoute you can do:
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={ (props) =>
( console.log(props.extra) || 1) ? (
<Component {...props} {...rest} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
)
}
/>
);
then
<PrivateRoute exact path="/protected" component={Main} extra="Boo!"/>
and Main should now receive extra prop (together with path and exact).

Related

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>
);
}
}

Log in page does not render with React Router ProtectedRoute

I have a react router app:
export default () => (
<Router basename={process.env.REACT_APP_BASENAME || ""}>
<div>
{routes.map((route, index) => {
return (
<PrivateRoute
key={index}
path={route.path}
exact={route.exact}
component={props => {
return (
<route.layout {...props}>
<route.component {...props} />
</route.layout>
);
}}
/>
);
})}
</div>
</Router>
);
and this will render dfferent views based on the route clicked. the routes will render based on this object in a routes.js file:
export default [
{
path: "/login",
layout: DefaultLayout,
component: LogIn
}, .....]
To build in some authentication, I defined a PrivateRoute as:
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={(props) => (
fakeAuth.isAuthenticated === true
? <Component {...props} />
: <Redirect to='/login' />
)} />
)
however, when i set the app as using PrivateRoute instead of normal Route (in the first snippet), the redirect does not use the routes object. How do I change the PrivateRoute const for a log in page reflect my original React Route architecture? what is the best practice?
Your code looks fine, but since you said your routes object is not understood by react-router maybe there is the case that your components aren't defined properly. For example, your components may be defined after the object is created. In that case, when that object is created, it will refer to undefined components. I made this mistake once, so I am just sharing what possibly went wrong.
Here is an example:
import ReactDOM from "react-dom";
import React, { Component } from "react";
import {
BrowserRouter as Router,
Route,
Link,
Redirect,
withRouter
} from "react-router-dom";
function Public() {
return <h3>Public</h3>;
}
function Protected() {
return <h3>You can see protected content</h3>;
}
class Login extends Component {
state = { redirectToReferrer: false };
login = () => {
fakeAuth.authenticate(() => {
this.setState({ redirectToReferrer: true });
});
};
render() {
let { from } = this.props.location.state || { from: { pathname: "/" } };
let { 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>
);
}
}
const routes = [
{
path: "/public",
component: Public,
private: false
},
{
path: "/login",
component: Login,
private: false
},
{
path: "/protected",
component: Protected,
private: true
}
];
function AuthExample() {
return (
<Router>
<div>
<AuthButton />
<ul>
<li>
<Link to="/public">Public Page</Link>
</li>
<li>
<Link to="/protected">Protected Page</Link>
</li>
</ul>
{routes.map((route, index) => {
if (route.private)
return (
<PrivateRoute
key={index}
path={route.path}
exact={route.exact}
component={props => {
return <route.component {...props} />;
}}
/>
);
return (
<Route
key={index}
path={route.path}
exact={route.exact}
component={props => {
return <route.component {...props} />;
}}
/>
);
})}
</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>
)
);
function PrivateRoute(props) {
const { component: Component, ...rest } = props;
return (
<Route
{...rest}
render={props =>
fakeAuth.isAuthenticated ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
)
}
/>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<AuthExample />, rootElement);
Notice Public, Protected and Login components are defined above the routes object. Defining them after the routes will result in errors.
I suggest to change your private route as following
const PrivateRoute = ({ component: Component, ...rest }) => fakeAuth.isAuthenticated === true ? (
<Route {...rest} component={component}
)} />
) : <Redirect to='/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.

Authentication in ReactJS using React-Router

I have a simple route.js file
const PrivateRoute = ({ component, ...rest }) => {
const isAuthed = localStorage.getItem('Authorization')
return (
<Route {...rest} exact
render = {(props) => (
isAuthed ? (
<div>
{React.createElement(component, props)}
</div>
) :
(
<Redirect
to={{
pathname: '/login',
state: { from: props.location }
}}
/>
)
)}
/>
)
}
class App extends Component {
componentWillMount() {
if (localStorage.getItem('Authorization')) {
history.push(`${history.location.pathname}`)
}
}
render() {
return (
<Router history={history}>
<div className="App-pageContainer">
<Route exact path="/" render={() => <Redirect to="/login" />} />
<Route path={'/login'} component={Login} />
<PrivateRoute path={'/dashboard'} component={Dashboard} />
</div>
</Router>
)
}
}
export default App
What I need is to put condition if the user has a key in localStorage(Authentication) then I want to redirect it to /dashboard if it doesn't contain Authentication in localStorage then I want to redirect it to /login.
I am totally stuck with this from past few days. Please help!!!
I think this kind of questions is too broad for an answer.
However, you could follow this amazing post to be able to implement that functionality.
Protected routes and authentication with React Router v4
This is what you got after finishing
import React from 'react'
import {
BrowserRouter as Router,
Route,
Link,
Redirect,
withRouter
} from 'react-router-dom'
const fakeAuth = {
isAuthenticated: false,
authenticate(cb) {
this.isAuthenticated = true
setTimeout(cb, 100)
},
signout(cb) {
this.isAuthenticated = false
setTimeout(cb, 100)
}
}
const Public = () => <h3>Public</h3>
const Protected = () => <h3>Protected</h3>
class Login extends React.Component {
render() {
return (
<div>
Login
</div>
)
}
}
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={(props) => (
fakeAuth.isAuthenticated === true
? <Component {...props} />
: <Redirect to='/login' />
)} />
)
export default function AuthExample () {
return (
<Router>
<div>
<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>
)
}

React Routing not rendering

I am using React routing v4 for a application that has a login and a home page once a dumb auth is done.
As of this point I have this LoadComponent.jsx in my index.js file:
class LoadComponent extends Component {
state = {
isLoggedOn: false,
};
onLoginCheck = (name, password) => {
console.log(name, password);
if (name && password) {
setTimeout(name, 100); // fake async
console.log('set the timeout');
}
this.setState({
isLoggedOn: true,
});
};
checkAuth = () => {
const { isLoggedOn } = this.state;
console.log('checking auth: ', isLoggedOn);
return (isLoggedOn);
};
render() {
return (
<BrowserRouter >
<Switch>
<Header isLogged={this.checkAuth()} />
<Route path="/login" render={props => <Login isLoggedOn={this.state.isLoggedOn} onLoggedInCheck={this.onLoginCheck} {...props} />} />
<PrivateRoute path="/" component={App} authenticated={this.state.isLoggedOn} />
</Switch>
</BrowserRouter>
);
}
}
My privateRouter looks like the following :
const PrivateRoute = ({
component, exact = false, path, authenticated,
}) => {
console.log('here : ', authenticated);
return (
<Route
exact={exact}
path={path}
render={props => (
authenticated ? (
React.createElement(component, props)
) : (
<Redirect to={{
pathname: '/login',
state: { from: props.location },
}}
/>
)
)}
/>
);
};
export default PrivateRoute;
The only thing that is rendered on the page is the Header component which makes sense, but the PrivateRoute component is not functioning since at first the Login component should be displaying. Im not sure what I am doing wrong here since I have followed the react router Redirect Auth example to some degree.

Resources