React router private route not rendering - javascript

I'm trying to set up protected routes using react router v4.
I have the following, which works fine.
function PrivateRoute({ component: Component, authed, ...rest }) {
return (
<Route
{...rest}
render={props =>
authed === true ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: '/', state: { from: props.location } }} />
)
}
/>
);
}
However, when I change this code to:
type PrivateRouteProps = {
component: Component,
};
const PrivateRoute = ({ component, authed, ...rest }: PrivateRouteProps) => (
<Route
{...rest}
render={props =>
authed === true ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: '/', state: { from: props.location } }} />
)
}
/>
);
I get the error: TypeError: instance.render is not a function. When I change to (note the component: Component), everything works:
const PrivateRoute = ({ component: Component, authed, ...rest }) => (
<Route
{...rest}
render={props =>
authed === true ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: '/', state: { from: props.location } }} />
)
}
/>
);
The render function of App.js is as follows:
render() {
return this.state.loading === true ? (
'loading'
) : (
<BrowserRouter>
<div>
{this.renderHeader()}
<Switch>
<Route exact path="/" component={LandingPage} />
<PrivateRoute
authed={this.state.authed}
path="/home"
component={HomePage}
/>
<Route component={PageNotFoundPage} />
</Switch>
</div>
</BrowserRouter>
);
}
Why does the arrow function with PrivateRouteProps not work as expected?

In your second example you try to render the component you passed in with
<Component {...props}/>
From the way you defined your flow type I guess that you imported Component like this:
import React, {Component} from 'react'.
That means that Component does not refer to the component passed in the component prop but still to the class Component imported from react because you did not shadow it anywhere inside your functional component. Even if you imported Component in your first example it would still work because you shadowed the name Component with the value of the prop component. In the second example you did not do that.
This is why you get the error because the react class Component neither has a render() method nor does it have any other functionality you expected there.
You need to assign the prop component to another name that is capitalized, e.g. Node and then render that variable. Note that the name needs to be capitalized. Otherwise it will be interpreted as usual html node and not as a react component:
type PrivateRouteProps = {
component: Component,
};
const PrivateRoute = ({ component: Node /* assign it to a capitalized name */, authed, ...rest }: PrivateRouteProps) => (
<Route
{...rest}
render={props =>
authed === true ? (
{/* Do not use `Component` here as it refers to the class imported from react and not to your component */}
<Node {...props} />
) : (
<Redirect to={{ pathname: '/', state: { from: props.location } }} />
)
}
/>
);
You could of course also use Component as a name which will shadow the name Component from the outer scope but this is bad practice as it often leads to confusion.

Related

React: private route not navigating

I am using a private route to navigate to a route after the user has logged in. However, I am facing an issue. I don't know why but my router is not transitioning to the desired route. Here's my code:
Routes.js
...
...
<PrivateRoute
authenticated={localStorage.getItem("isAuthenticated")}
path="/dashboard"
component={DashBoard}
exact
></PrivateRoute>
PrivateRoute.js
const PrivateRoute = ({ component: Component, authenticated, ...rest }) => (
<Route
{...rest}
render={props =>
authenticated ? (
<Component {...rest} {...props} />
) : (
<Redirect
to={{
pathname: '/',
state: { from: props.location }
}}
/>
)
}
/>
);
export default PrivateRoute;
Login.js
localStorage.setItem("isAuthenticated", true);
this.props.history.push('/dashboard');
Any help would be appreciated. Thanks!
So, I found the solution.
authenticated={localStorage.getItem("isAuthenticated")}
the above was invoking the method at application bootstrap due to which I was having the value of null being stored in my authenticated variable so, I changed it to arrow function and passed the argument without invoking it like below:
authenticated={() => localStorage.getItem("isAuthenticated")}
Can you just try it?
PrivateRoute.js
const PrivateRoute = ({ component: Component, authenticated, ...rest }) => {
console.log("authenticated",authenticated)//is it true or false?
if (authenticated=="true")
return (
<Route {...rest}>
{" "}
<Component {...rest} {...props} />
</Route>
);
else
return (
<Redirect
to={{
pathname: "/",
state: { from: props.location },
}}
/>
);
};
export default PrivateRoute;

Children Component undefined

See the below function i am creating the Auth routes and getting the children undefined and shows blank page. In App.js i am using the private route as you can see below and when i use simple Route instead of PrivateRoute its shows the Login component
<PrivateRoute exact path="/" name="Login" render={props => <Login {...props}/>} />
Here is is My PrivateRoute.js. When i console the children its shows undefined
function PrivateRoute({ children, ...rest }) {
const token = cookie.get('token');
return (
<Route
{...rest}
render={({ location }) =>
!token ? (
children
) : (
<Redirect
to={{
pathname: "/dashboard",
state: { from: location }
}}
/>
)
}
/>
);
}
export default Private Route;
I usually use something like it. Let me know if it works for you.
Here is my privateRoute.js component:
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import { isAuthenticated } from 'auth';
export default function PrivateRoute({ component: Component, ...rest }) {
return (
<Route {...rest} render={
props => (
isAuthenticated() ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: '/', state: { from: props.location } }} />
)
)} />
);
}
Here I have another file in the root auth.js:
export const isAuthenticated = () => {
return Boolean(localStorage.getItem('jwttoken'));
}
export const login = ({email, password}) => {
// Logic
localStorage.setItem('jwttoken', 'jkdsalkfj');
return true;
}
export const logout = () => {
localStorage.removeItem('jwttoken');
}
You can call it like:
<PrivateRoute
component={ProjectPage}
path="/project/:id"
/>
Children refer to what's inside the tag
<PrivateRoute>
<PrivateComponent/>
</PrivateRouter>
Here your children would be <PrivateComponent/>
You're not passing anything inside your PrivateRoute in your example. So you'll have undefined

support render props of react router in route hoc

I am writing a HOC for public route and private route. If the route is private and the user is authenticated then let him/her enter that page else redirect to login component. If the route is public and the user is not authenticated then show the page and also show the login page if the user is not authenticated but user is authenticated and still goes to login page then redirect the user to root page. This is working fine. But if i use the render instead of component, then it does not work. I could make it work only if i pass the component from the props called component of react-router.
How can i make it work if user user render props?
Here is my code
<Switch>
<PrivateRoute
exact
path="/"
render={() => <Home name="something" />} {/* this does not work */}
/>
<PrivateRoute exact path="/demo" component={Demo} />
<PublicRoute restricted={true} path="/auth" component={Authentication} />
</Switch>
PublicRoute.js
const PublicRoute = ({component: Component, restricted, ...rest}) => {
return (
<Route
{...rest}
render={props =>
isLogin() && restricted ? <Redirect to="/" /> : <Component {...props} />
}
/>
)
}
PrivateRoute.js
const PrivateRoute = ({component: Component, ...rest}) => {
return (
<Route
{...rest}
render={props =>
isLogin() ? <Component {...props} /> : <Redirect to="/auth/login" />
}
/>
)
}
Also if there is any additional things to improve, please do suggest me.
The problem is that in your custom routes you are always using the component prop. So when passing the render prop it is overruled by the one in your custom route and thus trying to render the provided component.
When you modify it like the function below, it will work. It also extracts the render prop and if it's a function it will use that instead of the component prop.
const PrivateRoute = ({component: Component, render, ...rest}) => {
const renderContent = props => {
if (!fakeAuth.isAuthenticated) {
return (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
)
}
return (typeof render === 'function') ? render(props) : <Component {...props} />
}
return (
<Route {...rest} render={renderContent} />
);
}

Component (created by Route) giving inst.render is not a function error

I have a PrivateRoute component that try to render a Component as follows:
import React, { Component } from 'react';
import { Route, Redirect } from 'react-router-dom';
const PrivateRoute = ({ component, loggedIn, ...rest }) => (
<Route
{...rest}
render={props =>
loggedIn ? (
<Component {...props} />
) : (
<Redirect
to={{ pathname: '/login', state: { from: props.location } }}
/>
)}
/>
);
export default PrivateRoute;
And I use this component like this:
<PrivateRoute loggedIn={!!token} path="/user" component={User} />
It gives me the error as the title. I wonder where could I go wrong?
Thank you in advance.
You have unpacked the component that you passed to PrivateRoute as component:
const PrivateRoute = ({ component, loggedIn, ...rest }) => (
// HERE ^
but then you use Component (note the capital C) which is imported from react to render it.
<Component {...props} />
And that imported Component doesn't have any render method. Hence the error you get.
To fix you need to rename your unpacked component like the following:
const PrivateRoute = ({ component: Comp, loggedIn, ...rest }) => (
// HERE ^
<Route
{...rest}
render={props =>
loggedIn ? (
<Comp {...props} />
// HERE ^
) : (
<Redirect
to={{ pathname: "/login", state: { from: props.location } }}
/>
)
}
/>
);

Converting React function component to class component issue

I have the following react functional component to help support authentication required routes with react-router.
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
isAuthenticated() ? (
<Component {...props}/>
) : (
<Redirect to={{
pathname: '/login',
state: {from: props.location }
}}/>
)
)}/>
)
I need to convert this from a functional component to a class component so I can take advantage of the componentDidMount method of React.Component. Unfortunately I'm having trouble figuring out how to migrate this. If I take it as is I need to replicate the Component and ...rest parameters, but I'm not sure how to do that. I think I can get the Component parameter with this.props.component, but I'm not sure how to pull ...rest. I'm new to JSX and ES6 so any help or guidance would be much appreciated.
The functional component is the render function, therefore:
class PrivateRoute extends React.Component {
render() {
const {component: Component, ...rest} = this.props;
return (
<Route {...rest} render={props => (
isAuthenticated() ? (
<Component {...props}/>
) : (
<Redirect to={{
pathname: '/login',
state: {from: props.location }
}}/>
)
)}/>
);
}
}
or, written a bit more readable:
class PrivateRoute extends React.Component {
render() {
const {component: Component, ...rest} = this.props;
const renderRoute = props => {
if (isAuthenticated()) {
return (
<Component {...props} />
);
}
const to = {
pathname: '/login',
state: {from: props.location}
};
return (
<Redirect to={to} />
);
}
return (
<Route {...rest} render={renderRoute}/>
);
}
}
A nice, clean refactor by extending the Route component:
class PrivateRoute extends Route {
render() {
return isAuthenticated()
? super.render()
: <Redirect to={{
pathname: '/login',
state: {from: props.location}
}}/>;
}
}
If you use this, you have to wrap your <PrivateRoute/>s in a <Switch/>, as below. Otherwise, you will have duplicate redirects and the page will fail to load.
<Router>
<Navbar/>
<SideDrawer/>
<Switch>
<Route path="/tokens" component={Login}/>
<PrivateRoute exact path="/" component={ExampleComponent}/>
<PrivateRoute path="/users" component={Users}/>
<PrivateRoute path="/software" component={Software}/>
</Switch>
</Router>
For your case useEffect hook will be the best choice.
You can simply add use effect hook to your component
const PrivateRoute = ({ component: Component, ...rest }) => {
useEffect(()=>{
// your logic for componentDidMount here
}, []);
return (
<Route {...rest} render={props => (
isAuthenticated() ? (
<Component {...props}/>
) : (
<Redirect to={{
pathname: '/login',
state: {from: props.location }
}}/>
)
)}/>
)
}
#Sulthans answer is correct and directly answers your question. But I would like to put the answer differently with a suggestion.
Based on your question your requirement is something like using life cycle hooks ie componentDidMount in your functional component.
I would like to suggest continuing to use the functional component and getting the hook implemented using useEffect provided if you are using the latest react
useEffect(() => {
console.log('mount it!');
}, []); // passing an empty array as second argument triggers the callback in useEffect only after the initial render thus replicating `componentDidMount` lifecycle behaviour
I recommend this due to the following reasons
It reduces the boilerplate
It promotes readability
React team nowadays promotes the use of function components. Recommend reading gradual adoption strategy.

Categories

Resources