Inside my React JS project, I am working on the PrivateRoutes
. I have gone through this example of private routing and authenticating using react-router-dom
.
https://reacttraining.com/react-router/web/example/auth-workflow
According to this documentation, they have created a PrivateRoute
as a stateless component.
But my requirement is to convert it to stateful React component as I want to connect my PrivateRoute
component to redux store.
Here is my code.
stateless component
import React from 'react';
import {Route, Redirect} from 'react-router-dom';
import {auth} from './Authentication';
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={props =>
auth.isAuthenticated ? (
<Component {...props} />
) : (
<Component {...props} action="login"/>
)
}
/>
);
export default PrivateRoute;
I converted this component to stateful React
component like this.
stateful React component
import React from 'react';
import {Route, Redirect} from 'react-router-dom';
import {auth} from './Authentication';
import {connect} from 'react-redux';
class PrivateRoute extends React.Component {
render({ component: Component, ...rest }) {
return (
<Route
{...rest}
render={props =>
this.props.customer.isAuthenticated ? (
<Component {...props} />
) : (
<Component {...props} action="login"/>
)
}
/>
);
}
}
export default connect(state => state)(PrivateRoute);
Here, I am reading the data from redux store to check whether the user is authenticated or not.
But the way I am converting the stateless component to stateful isn't correct.
Am I passing the arguments render({ component: Component, ...rest })
correctly?
Will connecting the PrivateRoute
with redux store create any problem with props
as state=>state
will map state
to props
as well as ...rest
will have props
object?
Not sure what is happening inside the code.
Update AppRouter.js
import React from 'react';
import {BrowserRouter, Route, Switch} from 'react-router-dom';
import {TransitionGroup, CSSTransition} from 'react-transition-group';
import PrivateRoute from './PrivateRoute';
import HomePage from './../components/HomePage';
import AboutUs from './../components/AboutUs';
import ContactUs from './../components/ContactUs';
import PageNotFound from './../components/PageNotFound';
import RestaurantList from '../components/RestaurantList';
import RestaurantMenu from '../components/RestaurantMenu';
import UserDetails from '../components/UserDetails';
import OrderConfirmation from '../components/OrderConfirmation';
import CustomerAccount from '../components/CustomerAccount';
import Logout from '../components/sections/Logout';
export default () => {
return (
<BrowserRouter>
<Route render={({location}) => (
<TransitionGroup>
<CSSTransition key={location.key} timeout={300} classNames="fade">
<Switch location={location}>
<Route path="/" component={HomePage} exact={true}/>
<Route path="/about" component={AboutUs} />
<Route path="/contact" component={ContactUs} />
<Route path="/restaurants" component={RestaurantList} />
<Route path="/select-menu" component={RestaurantMenu} />
<PrivateRoute path="/user-details" component={UserDetails} />
<PrivateRoute path="/order-confirmation" component={OrderConfirmation} />
<PrivateRoute path="/my-account" component={CustomerAccount} />
<PrivateRoute path="/logout" component={Logout} />
<Route component={PageNotFound} />
</Switch>
</CSSTransition>
</TransitionGroup>
)} />
</BrowserRouter>
);
}
In general, converting a stateless functional component (SFC) to a Component
is done like this:
Create the class
shell for it.
Copy the SFC's body to the render
method. If the SFC was an arrow function, add a return
as necessary to render
.
Change any references to props
in the render
method to this.props
(or just add const { props } = this;
at the top). SFCs receive their props in their arguments, but a component receives them as arguments to its constructor; the default constructor will save them as this.props
.
In your case, it's using destructuring on its arguments, so you could do the same with this.props
on the right-hand side of the destructuring:
const { component: Component, ...rest } = this.props;
That's it. In your code, you've added parameters to the render
function, but it doesn't get called with any arguments, and you've only changed props
to this.props
a bit haphazardly (including changing auth.isAuthenticated
to this.props.customer.isAuthenticated
for some reason).
So applying 1-3 above:
// #1 - the shell
class PrivateRoute extends React.Component {
// #2 - `render`, with the body of the SFC inside
render() {
// #3 - destructure `this.props`
const { component: Component, ...rest } = this.props;
// #2 (part 2) - add `return`
return <Route
{...rest}
render={props =>
auth.isAuthenticated ? (
<Component {...props} />
) : (
<Component {...props} action="login"/>
)
}
/>;
}
}
Your stateful component should be:
class PrivateRoute extends React.Component {
render() {
const { component: Component, ...rest } = this.props;
return (
<Route
{...rest}
render={props =>
this.props.customer.isAuthenticated ? (
<Component {...props} />
) : (
<Component {...props} action="login"/>
)
}
/>
);
}
}
Please see that there is some issue in render
parameter of Route
. Here you have props
as function param but still using this.props.customer
, don't know the use case hence please fix it as per your application.
Apart from it Component
and all the other data is already there in props of the component. It won't be available in parameter of render
method in component. Same destructuring as available in stateless component can be written in render
method as shown in code above.
Will connecting the PrivateRoute with redux store create any problem with props?
Yes, it would. The way you have connected to the store will make store data available in props
of component but external props
passed to component will not be available.
For that you have to handle it in mapStateToProps
function:
const mapStateToProps = (state, ownProps) => ({
...state,
...ownProps
});
Here mapStateToProps
has second parameter which has the external own props passed to component. So you have to return it as well to make it available in component props
.
Now connect would be like:
export default connect(mapStateToProps)(PrivateRoute);
I was having two queries.
1) How to convert to Stateful Functional Component? 2) After connecting to the redux store will the props create a problem?
My first query was solved by the answer provided by TJCrowder
.
For a second query, I tried connecting the redux store
to the PrivateRoute
and I did get the data I was looking for.
Here is the code which worked for me.
import React from 'react';
import {Route, Redirect} from 'react-router-dom';
import {connect} from 'react-redux';
class PrivateRoute extends React.Component {
render() {
const { component: Component, ...rest } = this.props;
const {customer} = this.props;
return <Route
{...rest}
render={props =>
customer.isAuthenticated ? (
<Component {...props} />
) : (
<Component {...props} action="login"/>
)
}
/>;
}
}
export default connect(state => state)(PrivateRoute);
Using this code I got the data that is coming from the routes, as well as the redux state inside the props.
This is getting data coming from the routes const { component: Component, ...rest } = this.props;
This is the data coming from the redux store. const {customer} = this.props;
@TJCrowder has already written how to convert stateless component to stateful component in those 3 steps. so i will just write about connecting component to redux store like you did.
I think connected components should always define mapStateToProps
and explicitly declare which data they depend on from the state.
because the connected component rerenders if the connected property changes. so it would be a bad idea to connect the whole application state to a component. as it would mean that wheneever anything changes in application state rerender all connected components.
better we define explicitly like the following that we depend on a property called data
(or anything you have) from the state. so in this case this component will only rerender if state.data
changes it wont rerender if state.xyz
changes.
and this way you can take state.data and name it as you wish so it would not conflict with any existing props of the component.
const mapStateToProps = (state, ownProps) => ({
data: state.data
});
export default connect(mapStateToProps)(PrivateRoute);
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.