I cant seem to understand the proper way of dealing with guest and authorized routes. I checked out the given example https://reacttraining.com/react-router/web/example/auth-workflow but that dosnt block the authorized user to open /login route and it seems all of the given examples do the same.
I tried doing something like this:
export default function requireGuest(WrappedComponent) {
class Guest extends Component {
render() {
if (this.props.authenticated) {
return <Redirect to="/agenda"/>
}
return <WrappedComponent {...this.props}/>
}
}
function mapStateToProps(state) {
return {authenticated: isLoggedInSelector(state)}
}
const withConnect = connect(mapStateToProps, null, null, {pure: false});
return compose(
withConnect,
withRouter
)(Guest);
}
class Authorized extends Component {
render() {
if (!this.props.authenticated) {
return <Redirect to="/login"/>
}
return <WrappedComponent {...this.props}/>
}
}
function mapStateToProps(state) {
return {authenticated: isLoggedInSelector(state)}
}
const withConnect = connect(mapStateToProps, null, null, {pure: false});
return compose(
withConnect,
withRouter
)(Authorized);
}
And then in the main container do something like this:
<Route path='/login' component={RequireGuest(LoginPage)}/>
<Route path='/register' component={RequireGuest(RegisterPage)}/>
<Route path="/dashboard" component={RequireAuthorization(DashboardPage)}/>
But when i try to navigate anywhere else the path updates and the state updates too, but there is no rerenders and no redirects.
Then I tried something like this:
class GuestRoutes extends Component {
render() {
const {loggedIn} = this.props;
return (
<Route
render={props =>
loggedIn ? (
<Redirect
to={{
pathname: "/agenda",
state: {from: props.location}
}}
/>
) : (
<Switch>
<Route path='/login' component={LoginPage}/>
<Route path='/register' component={RegisterPage}/>
<Redirect to='/login'/>
</Switch>
)
}
/>
);
}
}
class AuthorizedRoutes extends Component {
render() {
const {loggedIn} = this.props;
return (
<Route
render={props =>
loggedIn ? (
<Switch>
<Route exact path="/dashboard" component={DashboardPage}/>
<Redirect to="/dashboard"/>
</Switch>
) :
(<Redirect
to={{
pathname: "/login",
state: {from: props.location}
}}
/>)
}
/>
);
}
}
And in the main container
<Switch>
<GuestRoutes/>
<AuthorizedRoutes/>
</Switch>
But then if the user is logged in, the guest routes keep getting rendered until it gets the depth error.
Im been trying to figure this out for quite a few hours so im getting desperate here. So any examples or anything that has routes protected based on the authorization state
Related
Is it possible in React to conditionally render a inside a using this kind of component?
import PropTypes from 'prop-types'
import { useApplication } from 'ice-components'
const canEdit = (currentUser) => {
if (currentUser.settings.canEditSpecRec.value === 'False' || !currentUser.location.isSpecimenReceptionType) {
return false
}
return true
}
const Permissions = ({ children }) => {
const { currentUser } = useApplication()
if (!canEdit(currentUser)) {
return null
}
return children
}
Permissions.propTypes = {
children: PropTypes.node,
}
export { Permissions as default, canEdit }
And the route is made in this way:
<Switch>
<Route exact path component={component_1} />
<Route path='/anotherPath' component={component_2} />
<Switch>
I tried to wrap the Permissions component around a single Route component but it breaks. Any ideas?
A good way to conditionally render a Route is the one provided by the section Auth of the react-router documentation:
https://reacttraining.com/react-router/web/example/auth-workflow
What's different in your application is that you're not authorizing based on authentication but on some user permission, you could do something like this:
function PrivateRoute({ children, ...rest }) {
return (
<Route
{...rest}
render={({ location }) =>
canEdit(currentUser) ? (
children
) : (
<Redirect
to={{
pathname: "/",
}}
/>
)
}
/>
);
}
Wrapping the Route in a PrivateRoute component that will return your route if user has permission to or something else if not (maybe a redirect).
export default function AuthExample() {
return (
<Router>
<div>
<Switch>
<Route path="/public">
<PublicPage />
</Route>
<Route path="/login">
<LoginPage />
</Route>
<PrivateRoute path="/protected">
<ProtectedPage />
</PrivateRoute>
</Switch>
</div>
</Router>
);
}
I have this private route component that is used to render a component only is the user is logged in, else it should redirect to the login page.
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
authToken()
? <Component {...props} />
: <Redirect to={{ pathname: '/login', state: { from: props.location } }} />
)} />
)
export default withRouter(PrivateRoute);
and this is my main app:
<BrowserRouter>
<div className="wrapper">
<Switch>
<Route path="/login" component={LoginPage} />
<>
<div className="dashboard">
<SideBar />
{permittedEvents &&
<div className="content-area">
<PrivateRoute exact path="/" component={Dashboard} />
<PrivateRoute exact path="/calendar" component={Calendar} />
</div>
}
</div>
</>
</Switch>
</div>
</BrowserRouter>
for some reason the redirect is being ignored completely and when the user is not logged in, the Sidebar gets rendered but nor the content or the login page get rendered.
I have tried returning only the redirect in te Private route to force te redirect and check whether it was something wit my authentication. But the redirect doesn't seem to be working no matter where its included.
You don't need Route
class PrivateRoute extends React.Component {
constructor(props) {
super(props);
}
render() {
const { component: Component, ...rest } = this.props;
const redirectPath = (<Redirect to={{
pathname: "/login",
state: {
from: this.props.location.pathname
}
}}/>);
if (!ok) {
return redirectPath;
}
return <Component {...this.props} />;
}
};
export default withRouter(PrivateRoute);
When a user tries to view a private page(/products/1/edit), he will be redirected to the login component.
After login i want to redirect the user to the same product edit page. **The problem is that I am unable to get the location props in the component. It is returning as undefined.
** The code is as follows.
App.js
class App extends React.Component {
render() {
return (
<div className="App">
<AppRouter />
</div>
);
}
}
export default App;
AppRouter.js
import { ConnectedRouter } from 'react-router-redux';
...
class AppRouter extends Component {
componentDidMount() {
this.props.checkAlreadyLoggedIn();
}
render() {
const { token, location } = this.props;
return ((
<ConnectedRouter history={history}>
<Layout style={{ minHeight: '100vh' }}>
<Layout>
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/login" render={ () => token && <Redirect to=
{
location.state && location.state.from ? location.state.from.pathname :
"/dashboard"
} /> || <Login />} />
<PrivateRoute path="/dashboard" component={Dashboard} />
...
</Switch>
</Layout>
</Layout>
</ConnectedRouter>
));
}
}
history.js
import createHistory from 'history/createBrowserHistory';
const history = createHistory();
export default history;
i am already defined the private route as follows
class PrivateRoute extends PureComponent {
static propTypes = {
token: PropTypes.string,
component: PropTypes.any
}
render() {
const { component: Component, ...rest } = this.props;
let token = utils.getUserToken();
return (
<Route
{...rest}
render={ () =>
(token && <Component {...this.props} />) ||
<Redirect to={{
pathname: '/login',
state: { from: this.props.location }
}}/>
}
/>
)
}
}
Only if i get the location.state.from.pathname, i will be able to redirect the user to the page that he tried to access prior to login.
Any idea on how to fix this?
This is the PrivateRoute implementation I use and it performs redirect:
import React from 'react';
import { Route, Redirect } from "react-router-dom";
import { isLoggedIn } from '../../utils/authHelper';
class PrivateRoute extends React.Component {
renderComponent = () => {
const { path, component: Component } = this.props;
return (
isLoggedIn()
? <Component {...this.props} />
: <Redirect to={{
pathname: '/login',
state: { from: path }
}} />
)
}
render (){
const { component: Component, ...rest } = this.props;
return (
<Route {...rest} render={this.renderComponent} />
)
}
}
export default PrivateRoute;
And usage is like:
<Switch>
<Route path='/login' component={LoginScreen} />
<PrivateRoute path='/profile' component={ProfileComponent} />
</Switch>
And on /login route you will get the original url via this.props.location.state.from:
...
doLogin(username, password)
.then(() => {
window.location.href = `/${this.props.location.state.from}`;
});
Using react hooks useHistory() method history.goBack()
...
login(userData);
history.goBack();
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.
I'm using react router v4 with redux and i want to make use of private and protected route to redirect if user is not logged in.
i have this Routes component:
class Routes extends Component {
render() {
const { auth } = this.props;
// so checks against isAuthenticated can pass
if (auth.isAuthenticated !== undefined) {
return (
<div>
<Route exact component={() => <Home />} />
<PublicRoute
authed={() => auth.isAuthenticated}
path="/login"
component={(routerProps) => <Login {...routerProps} />}
/>
<PublicRoute
authed={() => auth.isAuthenticated}
path="/register"
component={(routerProps) => <Register {...routerProps} />}
/>
<PrivateRoute
authed={() => auth.isAuthenticated}
path="/dashboard"
component={Dashboard}
/>
</div>
);
} else {
return <div></div>
}
}
}
function mapStateToProps(state) {
return {
auth: state.auth
}
}
export default withRouter(connect(mapStateToProps)(Routes));
it is implemented like this:
class Main extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
store.dispatch(checkAuth());
}
render() {
return (
<Provider store={store}>
<Router>
<Theme>
<Routes />
</Theme>
</Router>
</Provider>
);
}
}
This is the PrivateRoute:
export function PrivateRoute({ component: Component, authed, ...rest }) {
const isAuthenticated = authed();
return (
<Route
{...rest}
render={props =>
isAuthenticated === true ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
)
}
/>
);
}
What is the best way to pass that auth prop, is it ok if i connect the Routes component and pass the auth from there, or should i be passed from somewhere else, maybe connect each component that needs it and read from there? Also how would this approach play with nested routes, like /dashboard/settings? Thanks
Actually, it is ok to use this type of private route in react, but you should check two moments:
I should check, that you do not have exact attribute, so all your routes like /dashboard/panel1, /dashboard/panel2 will be private to
auth.isAuthenticated}
path="/dashboard"
component={Dashboard}
/>
You will have some problem with connect. There is a simple fix for that:
export default connect(mapStateToProps, null, null, {
pure: false,
})(PrivateRoute);
more information here:
React router private routes / redirect not working