Converting React function component to class component issue - javascript

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.

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;

PrivateRoute on React is not redirecting when loggedIn user is false

I'm trying to implement some security into my app and ended up creating this code:
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
const PrivateRoute = ({
component: Component,
auth: { isAuthenticated, loading },
...rest
}) => (
<Route
{...rest}
render={props =>
!isAuthenticated && !loading ? (
<Redirect to='/auth/login' />
) : (
<Component {...props} />
)
}
/>
);
PrivateRoute.propTypes = {
auth: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
auth: state.auth
});
export default connect(mapStateToProps)(PrivateRoute);
auth comes from my state management that looks exactly like this when viewed from the Redux Devtools installed on my Chrome browser; here it is:
isAuthenticated and loading are usually true when a user is loggedIn; that works just fine. The problem I'm having is that my PrivateRoute does not redirect to the auth/login page when no one is loggedIn. Does anyone has any idea on how to fix this?. This is an example of one of my routes that need the PrivateRoute component:
<PrivateRoute exact path='/edit-basics' component={EditBasics} />
The route above is a page to edit the current loggedIn user info only available to him/her. I'm still accessing to it without being loggedIn.
So, you're probably getting errors from having the && operator and two expressions inside of the ternary operation that you're passing to the render method of Route.
You'll have to find a different way to validate if it's still loading.
In JSX if you pair true && expression it evaluates to expression – so basically you're returning !loading as a component to render.
Read more: https://reactjs.org/docs/conditional-rendering.html#inline-if-with-logical--operator
const PrivateRoute = ({
component: Component,
auth: { isAuthenticated, loading },
...rest
}) => (
<Route
{...rest}
render={props =>
// TWO OPERATIONS WITH && WILL CAUSE ERROR
!isAuthenticated && !loading ? (
<Redirect to='/auth/login' />
) : (
<Component {...props} />
)
}
/>
);
Also,
React Router's authors recommend constructing this kind of private route with child components instead of passing the component to render as a prop.
Read more: https://reacttraining.com/react-router/web/example/auth-workflow
function PrivateRoute({ auth, children, ...rest }) {
return (
<Route
{...rest}
render={() =>
!auth.isAuthenticated ? (
<Redirect
to={{
pathname: "/auth/login",
state: { from: location }
}}
/>
) : (
children
)
}
/>
);
}
And to call that route:
<PrivateRoute path="/protected" auth={auth} >
<ProtectedPage customProp="some-prop" />
</PrivateRoute>
It looks like you are not passing down the auth prop to the PrivateRoute. Try adding it.
<PrivateRoute exact path='/edit-basics' component={EditBasics} auth={this.props.auth}/>
maybe something like this
const PrivateRoute = ({ component,
auth: { isAuthenticated, loading },
...options }) => {
let history = useHistory();
useLayoutEffect(() => {
if ( !isAuthenticated && !loading) history.push('/auth/login')
}, [])
return <Route {...options} component={component} />;
};

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

React router private route not rendering

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.

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

Categories

Resources