Protected Route in React, not passing parameters - reactjs

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

Related

Unable to solve error showing as state undefined in reactjs?

I'm new to reactjs, my objective is to make protective routes and went through the link https://codesandbox.io/s/github/browniefed/tmp/tree/reactRouterProtectedRoute/ but in my code locally I'm getting an error like state is not defined. I couldn't able to figure it out where I'm doing wrong.
export const PrivateRoute = ({ component: Component, loggedIn, path, ...rest }) => (
<Route
path={path}
{...rest} render={props => {
// authorised so return component
return loggedIn ? (<Component {...props} />) : (<Redirect to={{
pathname: "/Signin",
state: {
prevLocation: path,
error: "You need to login first!",
},
}}
/>)
}} />
)
I'm getting error as shown in below snapshot:
Can anyone help me in solving this error?
You can wrap your component like this
=> withRouter(yourComponent);
If you just want a conditional (based on a signed flag) rendering use their component official example
function PrivateRoute({ children, ...rest }) {
return (
<Route
{...rest}
render={({ location }) =>
fakeAuth.isAuthenticated ? (
children
) : (
<Redirect
to={{
pathname: "/login",
state: { from: location }
}}
/>
)
}
/>
);
}
Then use the conditional component (Remember to define the "/login" route because it will default to this then the auth is false)
<PrivateRoute path="/protected">
<ProtectedConponent />
</PrivateRoute>
You can provide FakeAuth.isAuthenticated as your own way.
It seem that you want to pass your own state using the redirect component but you should not do that, if you want to pass arguments to the "secure component" do it when you declare it.
<PrivateRoute path="/protected">
<ProtectedConponent custom_prop={"my prop"}/> // < ---- here
</PrivateRoute>

User authentication in React Router

folks. I'm learning how to integrate React with Express using React Router, and I've run into a problem with authenticating users. I'm trying to use a higher order component to conditionally render a protected route based on a user's authorization status.
const ProtectedRoute = ({ component: Component, ...rest }) => {
return (
<Route
{...rest}
render={props => {
if (!AUTHORIZED) {
return <Redirect to="/login" />;
}
return <Component {...props} />;
}}
/>
);
};
The problem I'm having is in the if (!AUTHORIZED) statement. I'm using Passport to handle authentication on the Express server side, and I have an endpoint set up for retrieving user information and authorization status, but I can't figure out how to get access to that data before the page renders. If I was using a class component instead of a functional component, (learning hooks also), I think I could get the data with the componentWillMount lifecycle method, but I read that's bad practice. Any ideas on how I could move forward from here would be much appreciated!
***edit***
A couple of things I tried to get this working...
I tried adding an authorization module to fetch the data for me.
class Auth {
constructor() {
this.authenticated = false;
}
async isAuthenticated() {
console.log("hitting auth route");
await fetch("/api/auth")
.then(res => res.json())
.then(json => {
if (json.error) {
this.authenticated = false;
}
this.authenticated = true;
});
return this.authenticated;
}
}
export default new Auth();
I import the module and plug auth.authenticated() in place of the placeholder AUTHORIZED. This function gets skipped, because it's asynchronous, and the redirect will always occur.
So I need to add await to auth.authenticated(). But now I need to have async further up the chain, so I foolishly add async in front of props, as such:
render={async props => {
So now It's trying to render a promise object instead of a component, and we get the error Error: Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead.
Promises all the way down.
Answering this in case anyone runs into a similar issue...
The first solution I had was based on Firealem Erko's comment. On login, I saved a variable with the user's ID to local storage and referenced that in my component. This was a good first solution, but later was improved by something rotimi-best mentioned in his comment. It turns out you can indeed pass props to these components, which I did not realize in my inexperience. So that is now the way I'm doing it. The final solution is as follows:
const ProtectedRoute = ({
component: Component,
logged,
setLogged,
...rest
}) => {
return (
<Route
{...rest}
render={props => {
if (!logged) {
return (
<Redirect
to={{
pathname: "/login",
state: { flashInfo: "Please log in to continue." }
}}
/>
);
} else {
return <Component {...props} logged={logged} setLogged={setLogged} />;
}
}}
/>
);
};
And here's the parent component where I'm passing in the props:
function App() {
let [logged, setLogged] = useState(false);
useEffect(() => {
if (window.localStorage.getItem("qrs")) {
setLogged(true);
} else {
setLogged(false);
}
}, []);
return (
<div className="App">
<BrowserRouter>
<Nav logged={logged} setLogged={setLogged} />
<Switch>
<ProtectedRoute
exact
path="/dashboard"
component={Dashboard}
logged={logged}
setLogged={setLogged}
/>
<Route
path="/register"
exact
render={props => <Register {...props} logged={logged} />}
/>
<Route
path="/login"
exact
render={props => (
<Login {...props} logged={logged} setLogged={setLogged} />
)}
/>
</Switch>
</BrowserRouter>
</div>
);
}
Thanks to all the commenters for their suggestions!

How to pass key to react route?

I am separating routes based on roles. Each PrivateRoute is a route.
Route.tsx
export default function MCRoutes({ isUserAuthenticated, role }) {
const token = useFetchToken();
// const { data } = useFetchDataFromState("activeModule");
let privateRoutes = mcRoutesConfig[role].routes.map(
({ id, component, path, exact }) => (
<PrivateRoute
// key={id}
exact={exact}
isUserAuthenticated
path={path}
component={component}
/>
)
);
return (
<Switch>
{privateRoutes}
<Route component={NoPageFound} />
</Switch>
);
}
PrivateRoute
export default function PrivateRoute({
component: Component,
isUserAuthenticated,
...props
}) {
return (
<Route
{...props}
render={innerProps => {
return isUserAuthenticated ? (
<PrivateLayout>
<Component {...innerProps} />
</PrivateLayout>
) : (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
);
}}
/>
);
}
But when I do this, React asks me to provide key for each item in map function. And if I pass key as prop then it remounts components when route changes. How to solve this ?
My solution is to use the same key for all routes. No warning and seems like it works just fine.
const Routes = ({ routes }) => {
return (
<Switch>
{routes.map(props => {
return <RouteWithLayout key={1} {...props} />
})}
</Switch>
)
}
This is most probably working due to how Switch works. It'll check paths and renders the only one exactly that matches.
The map function in JS provides a second argument to the function you give it, which is the index of the current iteration. The React community suggests that you can use that if you cannot use other properties.
So you could do like this:
...
let privateRoutes = mcRoutesConfig[role].routes.map(
({ id, component, path, exact }, index) => (
<PrivateRoute
key={index}
exact={exact}
isUserAuthenticated
path={path}
component={component}
/>
)
);
return ...
That should work.

How to pass a value from mapStateToProps into a function?

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

React Router match property null when using custom Route component

I am using the ConnectedRouter component and a custom Route like so:
const PrivateRoute = ({ layout: Layout, component: Component, rest }) => (
<Route
{...rest}
render={matchProps => (
<AuthConsumer>
{({ authenticated }) =>
authenticated ? (
<Layout pathname={matchProps.location.pathname}>
<Component {...matchProps} />
</Layout>
) : (
<Redirect to={{ pathname: '/login', state: { from: matchProps.location } }} />
)
}
</AuthConsumer>
)}
/>
)
which I use like this:
<Switch>
<PrivateRoute
exact
path={`${match.url}someroute/:id`}
layout={Layout}
component={SomePage}
/>
</Switch>
And a component like so:
import React from 'react'
const SomePage = ({ match }) => {
console.log('MATCH ', match.params)
return <div>TESTING</div>
}
export default SomePage
In this case, match is empty, and it thinks its on the '/' route (even though location prop says its on /someroute/123.
On the other hand, this works:
<Route
exact
path={`${match.url}someroute/:id`}
component={SomePage}
/>
and match gets updated properly.
I'm stumped as to why this is happening. Any help would be appreciated!
Ah, I figure it out. rest should have been ...rest, the <Route> component wasn't getting the props passed down correctly!

Resources