React Router / Redux / HOC not working with 'render' prop

1.2k views Asked by At

I am using react router v4 and I'm trying to wrap my head around a react-router / redux / HOC related issue. I have a higher order component working. The HOC itself is connect()-ed to redux store. This approach works perfectly if I wire it up in a <Route /> via a component prop: <Route path="/profile" component={ withAuth(Profile) } /> does work.

However, when I try to do the same with a <Route /> and a render prop it does not work: <Route path="/profile" render={ () => withAuth(Profile) } /> The console throws "Route.render(): A valid React element (or null) must be returned. You may have returned undefined, an array or some other invalid object." It does work when I omit the HOC: <Route path="/profile" render={ () => <Profile /> } /> so I suspect a problem with the HOC but I can't find it.

The reason I'm trying to use render is I'd like to pass additional props to the HOC. Besides it bugs me that I can't find the bug.

Can anybody with a fresh eye have a look and put me on the right path? Thanks!

/* === app.js === */

import React, { Component } from 'react';
import { Route } from 'react-router-dom';
import { Provider } from 'react-redux';

import Header from './header';
import Home from './home';
import Content from './about';
import Profile from './profile';
import withAuth from './withAuth';

import store from '../reducers/store';

export default class App extends Component {
    render() {
        return (
            <Provider store={store}>
                <div className="mdl-grid">
                    <Header />
                    <main className="mdl-layout__content">
                        <div className="page-content">
                            <Route path="/" exact component={Home} />
                            <Route path="/about" component={Content} />
                            <Route path="/profile" render={ () => withAuth(Profile) } />
                        </div>
                    </main>
                </div>
            </Provider>
        )
    }
}



/* === withAuth.js (Higher order component) === */

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Redirect } from 'react-router-dom';

const HOC = WrappedComponent => {
    return class extends Component {
        render() {
            if (this.props.auth) {
                return <WrappedComponent authenticated={this.props.auth} {...this.props} />
            } else {
                return <Redirect to="/" />
            }
        }
    }
}

function mapStateToProps({ auth }) {
    return { auth };
}

export default WrappedComponent => connect(mapStateToProps)( HOC(WrappedComponent) );
2

There are 2 answers

0
Shubham Khatri On BEST ANSWER

The reason it doesn't work is because, here

<Route path="/profile" render={ () => withAuth(Profile) } />

render is actually assigned a function withAuth and not the returned value. What you need to do is

const AuthProfile = withAuth(Profile);

export default class App extends Component {
    render() {
        return (
            <Provider store={store}>
                <div className="mdl-grid">
                    <Header />
                    <main className="mdl-layout__content">
                        <div className="page-content">
                            <Route path="/" exact component={Home} />
                            <Route path="/about" component={Content} />
                            <Route path="/profile" render={ (props) => <AuthProfile {...props}/> } />
                        </div>
                    </main>
                </div>
            </Provider>
        )
    }
}

The difference between

render={ () => withAuth(Profile) } 

and

render={ (props) => <AuthProfile {...props}/> }

is that in the first case its an arrow function that is bound to the context. Whereas in the second case its a function returning a component

0
mradziwon On

I think your problem is with the way you use <Redirect />, you have to put it in <Route />. Look at this example:

const PrivateRoute = ({ component: Component, ...rest }) => (
  <Route {...rest} render={props => (
    fakeAuth.isAuthenticated ? (
      <Component {...props}/>
    ) : (
      <Redirect to={{
        pathname: '/login',
        state: { from: props.location }
      }}/>
    )
  )}/>
)