18

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.

Mayank Kumar Chaudhari
  • 16,027
  • 10
  • 55
  • 122
Chris Dellinger
  • 2,292
  • 4
  • 25
  • 33

4 Answers4

27

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}/>
       );
    }
}
michaelrbock
  • 1,160
  • 1
  • 11
  • 20
Sulthan
  • 128,090
  • 22
  • 218
  • 270
1

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>
Cameron Hudson
  • 3,190
  • 1
  • 26
  • 38
0

@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.
SAMUEL
  • 8,098
  • 3
  • 42
  • 42
0

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 }
        }}/>
    )
  )}/>
)
}
Mayank Kumar Chaudhari
  • 16,027
  • 10
  • 55
  • 122