How to pass a value from mapStateToProps into a function? - reactjs

I am trying to make the following link inaccessible if a user is not logged in:
<PrivateRoute path="/page" exact component={page}/>
React returns an error on this code:
const PrivateRoute = ({component: Component, ...rest}) => (
<Route {...rest} render={(props) => (
this.props.isAuthenticated === true
? <Component {...props} />
: <Redirect to='/login'/>
)}/>
);
And the is.Authenticated part
const mapStateToProps = state => {
return {
isAuthenticated: state.token !== null
};
};
TypeError: Cannot read property 'props' of undefined
I am trying to access the isAuthenticated passed from my mapStateToProps in the function PrivateRoute, how can I do that ?

First of all you should use rest instead of this.props (which is not defined)
rest.isAuthenticated === true
and not:
this.props.isAuthenticated === true
Then you have to pass the prop to the private route, something like this:
<PrivateRoute isAuthenticated={some_variable_here}> // According to your validation logic

Try this:
const PrivateRoute = ({component: Component, ...rest}) => (
<Route {...rest} render={() => (
!!rest.isAuthenticated === true
? <Component {...rest} />
: <Redirect to='/login'/>
)}/>
);

I have created my answer in combination of #Emanuele and #Sultan.
So First I changed my PrivateRoute from
<PrivateRoute path="/page" exact component={page}/>
to
<PrivateRoute path="/page" exact component={page} auth={this.props.isAuthenticated}/>
This way I can pass the value from mapStateToProps in the PrivateRoute
Then I edited the function PrivateRoute to get the value of auth from the PrivateRoute. It now looks like follows
const PrivateRoute = ({component: Component, ...rest}) => (
<Route {...rest} render={(props) => (
rest.auth !== false
? <Component {...props}/>
: <Redirect to='/'/>
)}/>
);
And now the privateRoute function is aware of the isAuthenticated value passed from the connect

Related

Create PrivateRoute with React-Router-Dom

I have seen lots of use cases about how people create private route with react-router-dom.
It usually looks like this:
const PrivateRoute = ({component: Component, auth:{ isAuthenticated }, ...rest}) => (
<Route {...rest} render={props => (
isAuthenticated ?
<Component {...props} />
: <Redirect to="/signin" />
)} />
);
And my question is: If I can use this way to do the same thing?
const PrivateRoute = ({component: Component, auth:{ isAuthenticated }, ...rest }) => (
isAuthenticated ? <Route {...rest} render={props => <Component {...props}/>}/> :<Redirect to="/signin" />
)
It seems working when I ran the code. But I still want to know why we do the condition check at the first way of writing? Is the second way of writing incorrect? If so, why? Thank you!
It's just a preference thing, either is fine and both will work. you can also make it even shorter and more readable like this if you like
const PrivateRoute = ({component: Component, auth:{ isAuthenticated }, ...rest }) => (
isAuthenticated ? <Component {...rest} /> : <Redirect to="/signin" />
)
and then render like so:
<PrivateRoute
exact
component={ComponentToRender}
path="/path"
aProp={'aProp'}
/>

Protected page using react-router-dom doesn't work

I am trying to implement only logged in user dashboard page.
App.js
<BrowserRouter history={hist}>
<Route path="/sign-in" exact component={SignIn} />
<PrivateRoute path="/dashboard">
<Dashboard />
</PrivateRoute>
</BrowserRouter>
PrivateRoute.js
export function PrivateRoute({children, ...props}) {
return (
<Route {...props} render={( {location} ) =>
isAuthenticated ? children : <Redirect to="/sign-in" />
}
/>
);
}
const isAuthenticated = () => {
const userToken = JSON.parse(sessionStorage.getItem("user-token"));
return userToken ? true : false'
}
I am checking sessionStorage for user-token, if it is null return false or true.
Event though it returns "false", it redirect to Dashboard, not Sign-in page.
What is my problem?
The reason why router redirects to dashboard even not authenticated user is that isAuthenticated is a function. Since that, you need to call this function:
export function PrivateRoute({children, ...props}) {
return (
<Route {...props} render={( {location} ) =>
isAuthenticated() ? children : <Redirect to="/sign-in" />
}
/>
);
}
const isAuthenticated = () => {
const userToken = JSON.parse(sessionStorage.getItem("user-token"));
return userToken ? true : false'
}
If you don't want to invoke function each time route changes, you can implement an immediately invoked function:
const isAuthenticated = (() => {
const userToken = JSON.parse(sessionStorage.getItem("user-token"));
return userToken ? true : false'
})()
export function PrivateRoute({children, ...props}) {
return (
<Route {...props} render={( {location} ) =>
isAuthenticated ? children : <Redirect to="/sign-in" />
}
/>
);
}
In PrivateRoute.js do this..
<Route {...props} render={( {location} ) =>
isAuthenticated() ? children : <Redirect to="/sign-in" />
}
/>
The reason is that you are not calling the isAuthenticated. If the problem still persists, feel free to discuss.

Conditional routing in React

I want to only be able to load a component if the user is authenticated to. Normally I have this PrivateRoute component for that:
const PrivateRoute = ({ component: Component, hasAccess, addUser, ...rest }) => (
<Route
{...rest}
render={props =>
hasAccess === true ? <Component {...props} addUser={addUser} /> : <Redirect to="/" />
}
/>
);
Which I call like this:
<PrivateRoute
hasAccess={hasAccess}
path="/settings"
component={Setting}
/>
But in the other case I can't reuse this code. So I decided to just declare the route like this:
<Route
createMeeting={createMeeting}
path="/meetings"
component={MeetingRoutes}
render={props =>
createMeeting === true ? <Component {...props} createMeeting={createMeeting} /> : <Redirect to="/" />
} />
It should act the same as the PrivateRoute if you ask me, but it doesn't. Instead I got this error message:
Warning: You should not use <Route component> and <Route render> in
the same route; <Route render> will be ignored
Can someone explain to me why it gives me this error? I can't find the solution for my problem.
It will better to write a reusable PrivateRoute:
const PrivateRoute = ({ component: Component, hasAccess, componentProps = {}, redirectTo = "/", ...rest }) => (
<Route
{...rest}
render={props =>
hasAccess ? <Component {...props} {...componentProps} /> : <Redirect to={redirectTo} />
}
/>
);
So with Settings you could use this route like this:
<PrivateRoute
hasAccess={hasAccess}
path="/settings"
component={Setting}
componentProps={{
addUser: addUser
}}
/>
and with MeetingRoutes like this:
<PrivateRoute
hasAccess={hasAccessToMeetingRoutes}
path="/meetings"
component={MeetingRoutes}
componentProps={{
createMeeting: createMeeting
}}
/>
Wrap your route in ternary operator and add one Redirect component at the end of routes.
You can use HOC to wrap component of route need authenticated other components no need authenticated you can render them normally
Example HOC authentication:
const loginGuard = Component =>
connect(state => ({
auth: getAuth(state),
}))(({ auth, ...props }) => {
if (auth) {
return <Component auth={auth} {...props} />;
}
// push to sign in if try access to component requires authentication
history.push('/sign-in');
return null;
});
Usage:
<Route
{...rest}
component={loginGuard(AdminComponent)}
/>
Just remove the component property in your new Route, like this:
const DynamicComp = MeetingRoutes
return <Route
createMeeting={createMeeting}
path="/meetings"
render={props =>
createMeeting === true ? <DynamicComp {...props} createMeeting={createMeeting} /> : <Redirect to="/" />
} />

Protected Route in React, not passing parameters

I am trying to create a protected route as shown in the code below:
const PrivateRoute = ({component:Component, authenticated, ...rest}) =>{
console.log(authenticated);
return(
<Route {...rest} render={(props) => (
authenticated === true
? <Component {...props} {...rest} />
: <Redirect to="/Login"/>
)} />);
}
export default PrivateRoute;
I am passing the following params in my Router configuration:
<PrivateRoute component={Appointments} authenticated={this.state.authenticated} exact path="/appointments" render={(props) => <Appointments {...props} appointments={this.state.appointments} />} />
.
However, when I try routing, it appears that the "appointments={this.state.appointments}" prop is not getting passed to the "Appointments" component.
This is the error that I get
TypeError: Cannot read property 'map' of undefined
Any idea what the issue could be?
I think the problem here is that you are passing props in the render property of the PrivateRoute which is as render={(props) => <Appointments {...props} appointments={this.state.appointments} />}. Now this property is not being Utilised in your actual component rendering in the PrivateRoute. Try following during your initialisation of route:
<PrivateRoute component={Appointments} authenticated={this.state.authenticated} exact path="/appointments" appointments={this.state.appointments} />
This should fix your issue. I would suggest here that rather than creating a PrivateRoute of your own, you can use a React HOC to create an authentication wrapper on the actual component.
function PrivateRoute({ children, ...rest }) {
const isAuthenticated = (location) => {
//if loading show loading indicator
}
const childrenWithLocation = (location) => Children.map(children, child => {
if (isValidElement(child)) {
return cloneElement(child, { to: child.props?.to + location.search });
}
return child;
});
return (
<Route
{...rest}
render={({ location }) =>
isAuthenticated(location)
? (childrenWithLocation(location))
: (<Redirect
to={{
pathname: "/login" + location.search,
state: { from: location }
}}
/>)
}
/>
);
}

How to add Typescript Interface for a ReactRouter's PrivateRoute?

Im using React Router v4 and has created a private route, along the following lines:
const PrivateRoute = ({ component: Component, ...rest }) => {
return (
<Route
{...rest}
render={(props) => {
return isUserLoggedIn() ? <Component {...props} /> : <Redirect to="/login" />;
}}
/>
);
};
And it is used like this:
<Switch>
<Route path="/login" component={Login} />
<PrivateRoute exact path="/" component={Home} />
<PrivateRoute path="/vehicles" component={Vehicles} />
</Switch>
Now, I get the following error:
Binding element 'Component' implicitly has an 'any' type.
This is because, I have given noImplicitAny AND I have not specified Interface type for this PrivateRoute props.
Question is, How do I add interface to the props here?
Note: It is especially the ...rest part in the props, that Im primarily confused at typescripting.
Try this:
export interface PrivateRouteProps extends RouteProps {
component: Component;
}
const PrivateRoute = (props: PrivateRouteProps) => {
let { component: Component, ...rest } = props;
return (
<Route
{...rest}
render={(props) => {
return isUserLoggedIn() ? <Component {...props} /> : <Redirect to="/login" />;
}}
/>
);
};

Resources