Creating PrivateRoute with 'component' prop instead of 'render' prop - reactjs

const PrivateRoute = ({ component, ...rest }) => {
return (
<Route
{...rest}
// OPTION 1
component={!loggedInUser ? <Redirect to="/login" /> : component}
// OPTION 2
render={()=>(!loggedInUser ? <Redirect to="/login" /> : component())}
/>
);
};
Which of the above do you prefer and why? I found that I wasn't able to use the new useParam() hook with OPTION 2 because it told me I was using a hook outside of a functional component body.

here is a good route gate that supports component, render, permission and redirect in case of missing the permission
import React from 'react';
import PropTypes from 'prop-types';
import { Redirect, Route } from 'react-router-dom';
/* eslint-disable no-nested-ternary */
const RouteGate = props => {
const {
component: Component,
hasPermission,
redirectTo,
render,
...rest
} = props;
return (
<Route
render={() =>
hasPermission ? (
Component ? (
<Component {...props} />
) : (
render()
)
) : (
<Redirect
to={{
pathname: redirectTo,
}}
/>
)
}
{...rest}
/>
);
};
RouteGate.propTypes = {
component: PropTypes.func,
render: PropTypes.func,
hasPermission: PropTypes.bool,
redirectTo: PropTypes.string,
};
RouteGate.defaultProps = {
hasPermission: true,
redirectTo: '',
};
export default RouteGate;
an example of using it with the component:
<RouteGate
path={paths.profile} // route of the profile, eg: /profile
component={Profile} // component of the profile page
hasPermission={isLoggedIn} // a flag in user selectors
redirectTo={paths.login} // route of the login page eg: /login
/>
an example of it with the render:
<RouteGate
path={paths.profile}
render={() => <Profile />}
hasPermission={isLoggedIn}
redirectTo={paths.login}
/>

Related

In React, how do I render a stateless component that is passed in as a prop?

I want to render a component based on whetehr a user is logged in
<PrivateRoute
authed={isAuthenticated} path="/unapproved-list/"
component={UnapprovedList}
/>
The PrivateRoute component is set up like so
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import { Route } from 'react-router-dom';
const PrivateRoute = ({component, authed, ...rest}) => {
console.log("in private route, authed: " + authed);
return (
<Route
{...rest}
render={(props) => authed === true
? <Component {...props} />
: <Redirect to={{pathname: '/login', state: {from: props.location}}} />}
/>
)
}
export default PrivateRoute;
However, when authentication is true, this line
Component {...props} />
is resulting in the below error
TypeError: instance.render is not a function
finishClassComponent
node_modules/react-dom/cjs/react-dom.development.js:17160
17157 | } else {
17158 | {
17159 | setIsRendering(true);
> 17160 | nextChildren = instance.render();
| ^
17161 |
17162 | if ( workInProgress.mode & StrictMode) {
17163 | instance.render();
Not sure how to structure my private route to properly render the component. I would prefer not to change the component structure, which is currently like the below
const UnapprovedList = () => {
const [unapprovedCoops, setUnapprovedCoops] = React.useState([]); ...
return (
<>
{loadErrors && (
<strong className="form__error-message">
{JSON.stringify(loadErrors)}
</strong>
)}
{loadingCoopData && <strong>Loading unapproved coops...</strong>}
<RenderCoopList link={"/approve-coop/"} searchResults={unapprovedCoops} columnOneText={"Unapproved Entities"} columnTwoText={"Review"} />
</>
)
}
export default UnapprovedList;
The <Component {...props} /> line refers to the Component class imported from the React instead of the component prop because of the first letter upper vs lower case.
Try the following:
const PrivateRoute = ({ component: WrappedComponent, authed, ...rest }) => {
console.log("in private route, authed: " + authed);
return (
<Route
{...rest}
render={(props) => authed === true
? <WrappedComponent {...props} />
: <Redirect to={{pathname: '/login', state: {from: props.location}}} />}
/>
)
}

How to hide nav bar component in reactjs using protected route

Hi there i have created a protected route in and passing navbar as parent component. then i passing child components in protected rout i want to hide navbar in some component on some action
Here is my protected.router.js code
import React from "react";
import { Route, Redirect } from "react-router-dom";
import Navbar from './component/Layouts/Navbar';
export const ProtectedRoute = ({
component: Component,
...rest
}) => {
return (
<Route
{...rest}
render={props => {
if (localStorage.getItem("loggedAsli") === "1") {
return <div> <Navbar/>
<Component {...props} /> </div>;
} else {
return (
<Redirect
to={{
pathname: "/login",
state: {
from: props.location
}
}}
/>
);
}
}}
/>
);
};
And this my App.js code
import React from "react";
import { BrowserRouter, Route } from "react-router-dom";
import { ProtectedRoute } from "./protected.route";
import Learn from "./component/Learn/Learn";
class App extends React.Component {
constructor(props) {
super();
this.updateUserLoginSetting = this.updateUserLoginSetting.bind(this);
this.state = {
userId: "",
instituteId: ""
};
}
updateUserLoginSetting = (userId, instituteId) => {
this.setState({ userId: userId, instituteId: instituteId });
};
render() {
return (
<BrowserRouter>
<div className="App">
<Route render={props => <Login />} exact path="/login" />
<ProtectedRoute exact path="/Learn" component={Learn} />
</div>
</BrowserRouter>
);
}
}
export default App;
How i can hide navbar in Learn Component
Please guide me.
is there any global state handling
You could just pass a prop (e.g. showNav) and use it inside ProtectedRoute
export const ProtectedRoute = ({
component: Component,
showNav = true, // added showNav prop
...rest
}) => {
return (
<Route
{...rest}
render={props => { // check if should showNav
if (localStorage.getItem("loggedAsli") === "1" && showNav) {
return <div> <Navbar/>
<Component {...props} /> </div>;
} else {
return (
<Redirect
to={{
pathname: "/login",
state: {
from: props.location
}
}}
/>
);
}
}}
/>
);
};
And pass false when it's Learn
// pass showNav as false to hide nav bar
<ProtectedRoute showNav={false} exact path="/Learn" component={Learn} />

How to use Redux inside a React Router stateless middleware?

When some user try to access any page of my application, a React Router middleware is called to check if the user is logged in. The problem is that to access the Redux store I need to use store.getState() and if I use store.listen(...) I receive an error ('component' doesn't exist).
const PrivateRoute = ({ component: Component, ...rest }) => { // Receive component and path
return (
<Route { ...rest } render={(props) => {
return store.getState().login.token.status === 200
? <Component { ...props } />
: <Redirect to="/login" />
}}
/>
)
}
// store.subscribe(PrivateRoute) // Error when some page is requested
export default PrivateRoute;
I know that to listen changes on this function I need to pass a component, but I think it's not possible because it's a middleware. Do I really need to listen for changes in this case, if yes, how can I do this (how to mock this type of thing) ?
Obs : Example <PrivateRoute path="/private" component={() => <h1>Private</h1>}/>
Try this.
const PrivateRoute = ({ component: Component, hasLogin, ...rest }) => (
<Route
{...rest}
render={props => (
hasLogin ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: '/login'
}}
/>
)
)}
/>
)
export default connect(state => ({
hasLogin: state.login.hasLogin
}))(PrivateRoute)
In the switch call like this
<PrivateRoute exact path='/' component={some page} />
Using Redux
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
const PrivateRoute = ({ component: Component, auth: auth, ...rest }) => (
<Route
{...rest}
render={props =>
auth.isAuthenticated === true ? (
<Component {...props} />
) : (
<Redirect to="/login" />
)
}
/>
);
PrivateRoute.propTypes = {
auth: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
auth: state.auth
});
export default connect(mapStateToProps)(PrivateRoute);
After login have you dispatch an action to store the the login values in store.
if yes check that.
and i think you can store the login token in localStorage read about WebStorage and easily get it back using store.get('keyname').

How can I rewrite this React Component as a pure function and get an error/warning free implementation with React Router?

I've been struggling with rewriting this react component to a pure function per my eslint error recommendations. My goal is to also get this to work with react router without it throwing a warning/error.
Here is the class component:
class PrivateRouteContainer extends React.Component {
render() {
const {
isAuthenticated,
component: Component,
...props
} = this.props
return (
<Route
{...props}
render={props =>
isAuthenticated
? <Component {...props} />
: (
<Redirect to={{
pathname: '/login',
state: { from: props.location }
}} />
)
}
/>
)
}
}
source: https://github.com/ReactTraining/react-router/blob/master/packages/react-router-redux/examples/AuthExample.js
Here is my eslint error:
Here is my attempt:
const PrivateRouteContainer = (props) => {
const {
isAuthenticated,
isLoggingIn,
} = props;
return (
<Route
{...props}
render={() =>
(isAuthenticated || isLoggingIn
? <Component {...props} />
: (
<Redirect to={{
pathname: '/login',
state: { from: props.location },
}}
/>
))
}
/>
);
};
This doesn't seem to work as I'm getting the error:
Warning: You should not use and in the same route; will be ignored
Here are my route definitions if it helps:
import React from 'react';
import PropTypes from 'prop-types';
import { Route, Switch } from 'react-router';
import { connect } from 'react-redux';
import { Redirect } from 'react-router-dom';
import { ConnectedRouter } from 'react-router-redux';
import PrivateRoute from './PrivateRoute';
import Login from '../containers/LoginPage';
import Home from '../containers/HomePage';
import List from '../containers/ListPage';
import Edit from '../containers/EditPage';
import { logout } from '../redux/auth/oauth/actions';
const ConnectedSwitch = connect(state => ({ location: state.routerReducer.location }))(Switch);
const mapStateToProps = state => ({
isLoggingIn: state.authReducer.isLoggingIn,
isAuthenticated: state.authReducer.isAuthenticated,
error: state.authReducer.error,
});
const Logout = connect(mapStateToProps)(({ dispatch }) => {
dispatch(logout());
return <Redirect to="/" />;
});
const Routes = props =>
(
<ConnectedRouter history={props.history}>
<ConnectedSwitch>
<Route path="/" exact component={connect(mapStateToProps)(Home)} />
<PrivateRoute path="/list" exact component={List} />
<PrivateRoute path="/edit" component={Edit} />
<Route path="/login" exact component={connect(mapStateToProps)(Login)} />
<Route path="/logout" exact component={Logout} />
</ConnectedSwitch>
</ConnectedRouter>
);
Routes.propTypes = {
history: PropTypes.shape({}).isRequired,
};
export default connect(mapStateToProps)(Routes);
This is just a typo as I think you understood "this" is not defined in a stateless component :
const {
isAuthenticated,
isLoggingIn,
} = props; // <-- remove this. here
const PrivateRouteContainer = (props) => (
<Route
{...props}
render={() =>
(props.isAuthenticated || props.isLoggingIn
? <Component {...props} />
: (
<Redirect to={{
pathname: '/login',
state: { from: props.location },
}}
/>
))
}
/>
)
Instead of passing authentication as props, have you considered creating a utils file, such as auth.js that easily returns if the user is validated or not?
If we tangle the authentication conditions within component disposition we're adding an additional layer which we're going to have to care about, which is having to care about the parent component's life cycle and how it evaluates to authentication. This also means you're passing to the <Route /> component, some props that it doesn't care or doesn't need to care about, and that could be problematic as well
To make things clearer, also try using the object destructuring property instead of declaring const inside of your component, as well as the rest operator. Pass to route only what route needs, and to <Component />, only what component needs. That could be causing errors,
The final result is something like this:
import { isAuthenticated } from 'utils/auth';
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
isAuthenticated() ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: '/login' }} />
)
)}/>
);
I'm not sure if this is the best approach or not, but it seems to be working...
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { Route } from 'react-router';
import { Redirect } from 'react-router-dom';
const RedirectToLogin = () => (
<Redirect to={{ pathname: '/login' }} />
);
const PrivateRouteContainer = props => (
<Route
{...props}
component={props.isAuthenticated || props.isLoggingIn ?
props.component :
RedirectToLogin}
/>
);
PrivateRouteContainer.propTypes = {
isAuthenticated: PropTypes.bool.isRequired,
isLoggingIn: PropTypes.bool.isRequired,
component: PropTypes.func.isRequired,
};
const PrivateRoute = connect(state => ({
isAuthenticated: state.authReducer.isAuthenticated,
isLoggingIn: state.authReducer.isLoggingIn,
}))(PrivateRouteContainer);
export default PrivateRoute;

React Router V4 redirect based on user role

I have two types of users isEmployer and isCandidate. When I login, my app is supposed to check the role of the user and then load the approprate routes plus redirect to the appropriate landing page. This all seems to be working correctly however the route only redirects on login or on registration if the user role isCandidate. If the user isEmployer the page doesn't change however the navigation options in the navbar do.
I can't see what I'm missing here. Any help?
Path: App.jsx
const App = appProps => (
<Router>
<ScrollToTop>
<div className="bgColor">
<NavBar {...appProps} />
<Grid className="main-page-container">
<Switch>
{/* candidate routes */}
<IsCandidate exact path="/candidate" component={CandidateProfileContainer} {...appProps} />
{/* employer routes */}
<IsEmployer exact path="/employer/dashboard" component={EmployerDashboardContainer} {...appProps} />
{/* IsPublic routes */}
<IsPublic exact path="/register" component={Register} {...appProps} />
<IsPublic exact path="/login" component={Login} {...appProps} /> {/* Page not found */}
<Route render={function () {
return <p>Page not found</p>;
}}
/>
</Switch>
</Grid>
</div>
</ScrollToTop>
</Router>
);
App.propTypes = {
loggingIn: PropTypes.bool,
isCandidate: PropTypes.bool,
isEmployer: PropTypes.bool
};
export default withTracker(() => {
const loggingIn = Meteor.loggingIn();
return {
loggingIn,
isCandidate: !loggingIn && !!Meteor.userId() && !!Roles.userIsInRole(Meteor.userId(), 'isCandidate'),
isEmployer: !loggingIn && !!Meteor.userId() && !!Roles.userIsInRole(Meteor.userId(), 'isEmployer')
};
})(App);
Path: isEmployer.jsx
import React from 'react';
import PropTypes from 'prop-types'; // ES6
import { Route, Redirect } from 'react-router-dom';
const IsEmployer = ({ loggingIn, isEmployer, component: Component, ...rest }) => (
<Route
{...rest}
render={(props) => {
if (loggingIn) return <div />;
return isEmployer ?
(<Component loggingIn={loggingIn} isEmployer={isEmployer} {...rest} {...props} />) :
(<Redirect to="/login" />);
}}
/>
);
IsEmployer.propTypes = {
loggingIn: PropTypes.bool,
isEmployer: PropTypes.bool,
component: PropTypes.func
};
export default IsEmployer;
Path: isCandiate.jsx
import React from 'react';
import PropTypes from 'prop-types'; // ES6
import { Route, Redirect } from 'react-router-dom';
const IsCandidate = ({ loggingIn, isCandidate, component: Component, ...rest }) => (
<Route
{...rest}
render={(props) => {
if (loggingIn) return <div />;
return isCandidate ?
(<Component loggingIn={loggingIn} isCandidate={isCandidate} {...rest} {...props} />) :
(<Redirect to="/login" />);
}}
/>
);
IsCandidate.propTypes = {
loggingIn: PropTypes.bool,
isCandidate: PropTypes.bool,
component: PropTypes.func
};
export default IsCandidate;
I think the idea is you can pass in a function that returns a component that is correct for each user state
such as component= { () => {some logic to determine which user then return the appropriate component} }
does that make sense?
I think this is one implementation not sure if it is the best
obviously the app will keep track of the state of the user which w.e global state container you use flux or redux etc..

Resources