General approach
I would create a HOC to handle this logic for all of your pages.
// privateRoute is a function... const privateRoute = ({ // ...that takes optional boolean parameters... requireLoggedIn = false, requireOnboarded = false, requireWaitlisted = false // ...and returns a function that takes a component... } = {}) => WrappedComponent => { class Private extends Component { componentDidMount() { // redirect logic } render() { if ( (requireLoggedIn && /* user isn't logged in */) || (requireOnboarded && /* user isn't onboarded */) || (requireWaitlisted && /* user isn't waitlisted */) ) { return null } return ( <WrappedComponent {...this.props} /> ) } } Private.displayName = 'Private(${ WrappedComponent.displayName || WrappedComponent.name || 'Component' })' hoistNonReactStatics(Private, WrappedComponent) // ...and returns a new component wrapping the parameter component return Private } export default privateRoute
Then you just need to change the way you export your routes:
export default privateRoute({ requireLoggedIn: true })(MyRoute);
and you can use this route the same way you use it today in the react router:
<Route path="/" component={MyPrivateRoute} />
Redirection logic
How you install this part depends on several factors:
- How do you determine if a user is logged in, is he on, is on a waiting list, etc.
- Which component you want to answer, where to redirect.
User Status Processing
Since you are using Apollo, you probably just want to use graphql to get this data in your HOC:
return graphql(gql' query ... ')(Private)
Then you can modify the Private component to get these details:
class Private extends Component { componentDidMount() { const { userStatus: { isLoggedIn, isOnboarded, isWaitlisted } } = this.props if (requireLoggedIn && !isLoggedIn) { // redirect somewhere } else if (requireOnboarded && !isOnboarded) { // redirect somewhere else } else if (requireWaitlisted && !isWaitlisted) { // redirect to yet another location } } render() { const { userStatus: { isLoggedIn, isOnboarded, isWaitlisted }, ...passThroughProps } = this.props if ( (requireLoggedIn && !isLoggedIn) || (requireOnboarded && !isOnboarded) || (requireWaitlisted && !isWaitlisted) ) { return null } return ( <WrappedComponent {...passThroughProps} /> ) } }
Where to redirect
There are several different places where you can handle this.
The easy way: routes are static
If the user is not logged in, you always want to direct to /login?return=${currentRoute} .
In this case, you can simply hardcode these routes into componentDidMount . Done.
The component is responsible
If you want the MyRoute component MyRoute determine the path, you can simply add some additional parameters to the privateRoute function and then pass them when exporting MyRoute .
const privateRoute = ({ requireLoggedIn = false, pathIfNotLoggedIn = '/a/sensible/default',
Then, if you want to override the default path, you will change your export to:
export default privateRoute({ requireLoggedIn: true, pathIfNotLoggedIn: '/a/specific/page' })(MyRoute)
Route is responsible
If you want to be able to transfer the path from routing, you want to receive details for them in Private
class Private extends Component { componentDidMount() { const { userStatus: { isLoggedIn, isOnboarded, isWaitlisted }, pathIfNotLoggedIn, pathIfNotOnboarded, pathIfNotWaitlisted } = this.props if (requireLoggedIn && !isLoggedIn) { // redirect to 'pathIfNotLoggedIn' } else if (requireOnboarded && !isOnboarded) { // redirect to 'pathIfNotOnboarded' } else if (requireWaitlisted && !isWaitlisted) { // redirect to 'pathIfNotWaitlisted' } } render() { const { userStatus: { isLoggedIn, isOnboarded, isWaitlisted }, // we don't care about these for rendering, but we don't want to pass them to WrappedComponent pathIfNotLoggedIn, pathIfNotOnboarded, pathIfNotWaitlisted, ...passThroughProps } = this.props if ( (requireLoggedIn && !isLoggedIn) || (requireOnboarded && !isOnboarded) || (requireWaitlisted && !isWaitlisted) ) { return null } return ( <WrappedComponent {...passThroughProps} /> ) } } Private.propTypes = { pathIfNotLoggedIn: PropTypes.string } Private.defaultProps = { pathIfNotLoggedIn: '/a/sensible/default' }
Then your route can be rewritten to:
<Route path="/" render={props => <MyPrivateComponent {...props} pathIfNotLoggedIn="/a/specific/path" />} />
Combine options 2 & 3
(This is an approach I like to use)
You can also let the component and route choose who is responsible. You just need to add privateRoute parameters for the paths, as we did to allow the component to solve. Then use these values as defaultProps , as we did when the route was responsible.
This gives you the flexibility to make decisions. Just note that passing routes as a props will take precedence over passing from a component to the HOC.
Now all together
Here is a snippet combining all of the concepts above for the ultimate HOC study:
const privateRoute = ({ requireLoggedIn = false, requireOnboarded = false, requireWaitlisted = false, pathIfNotLoggedIn = '/login', pathIfNotOnboarded = '/onboarding', pathIfNotWaitlisted = '/waitlist' } = {}) => WrappedComponent => { class Private extends Component { componentDidMount() { const { userStatus: { isLoggedIn, isOnboarded, isWaitlisted }, pathIfNotLoggedIn, pathIfNotOnboarded, pathIfNotWaitlisted } = this.props if (requireLoggedIn && !isLoggedIn) {
I use non-responsive static winch as stated in the official documentation .