简体   繁体   中英

Converting stateless React component having arguments to stateful

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:

  1. Create the class shell for it.

  2. Copy the SFC's body to the render method. If the SFC was an arrow function, add a return as necessary to render .

  3. 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM