'React Routes problem with Tags other than Route
Using "react-router-dom": "^5.3.2"
In my component I have the following code:
render() {
return <div role="main">
<Switch>
<Route path="/auth" exact component={Auth} />
<PrivateRoute path="/" exact component={() => <>Main page</>} />
<IsGranted for={[ 'superuser' ]}>
<PrivateRoute path="/servers" exact component={ServerList} />
</IsGranted>
{/* or even nested in a <></> */}
<Route component={_ => <h1>Not found</h1>} />
</Switch>
</div>
}
const PrivateRoute = ({ component:Component, ...rest }) =>
<Route {...rest} render={props => isAuthenticated() ? <Component {...props}/> : <Redirect to={{ pathname:'/auth', state:{ from:props.location } }}/> }/>
const IfGranted = ({ children }) => condition() ? children : null
The problem is, everything below the empty tag <> is ignored.
For example, I expect Not found be rendered, if I type in some gibberish in the URL.
That happens only, if the <>...</> is commented out.
Is there anything I can do here?
Solution 1:[1]
The only valid children of the Switch component are the Route component (including custom route components) and the Redirect component. React.Fragment breaks this abstraction. The Switch component returns the first child <Route> or <Redirect> that matches the location or in the case where a child component is neither, that child is returned and nothing below it is reachable, i.e. the "Not Found" route.
From what I see of your snippet there is no need for the React.Fragment wrapping the PrivateRoute component.
- Remove the extraneous fragment around the
"/servers"route. - Use the route's
componentprop for React component references, use therenderprop for inline functions. - Order the routes in inverse order by route path specificity, this removes the need to specify the
exactprop on every route. Since you have a "not found" catch all route, the home path"/"above it will need theexactprop to preclude it from all other generic matching.
Code
render() {
return <div role="main">
<Switch>
<Route path="/auth" component={Auth} />
<PrivateRoute path="/servers" component={ServerList} />
<PrivateRoute exact path="/" render={() => <>Main page</>} />
<Route render={() => <h1>Not found</h1>} />
</Switch>
</div>
}
PrivateRoute
To make the PrivateRoute align more with the Route component API it should take all the same props as a Route and render either a Route or Redirect component. Use the useLocation hook to access the current location object for redirect purposes.
Example:
const PrivateRoute = props => {
const location = useLocation();
return isAuthenticated()
? <Route {...props} />
: (
<Redirect
to={{
pathname:'/auth',
state: { from: location }
}}
/>
);
};
Update
To add the ability to use roles to restrict access I would either wrap the routed component directly, or create a Higher Order component, or abstract another PrivateRolesRoute component.
Using wrapper
<Switch> <Route path="/auth" component={Auth} /> <PrivateRoute path="/servers" render={props => ( <IsGranted for={['superuser']}> <ServerList {...props} /> </IsGranted> )} /> <PrivateRoute exact path="/" render={() => <>Main page</>} /> <Route render={() => <h1>Not found</h1>} /> </Switch>Using HOC
Create a HOC that takes a component to decorate and a roles array, and returns a wrapped component.
const withIsGranted = (Component, roles = []) => props => ( <IsGranted for={roles}> <Component {...props} /> </IsGranted> );Decorate the component you want to role protect.
export default withIsGranted(ServerList, ['superuser']);Use the exported decorated component in the route. It won't look different than it was previously.
<Switch> <Route path="/auth" component={Auth} /> <PrivateRoute path="/servers" component={ServerList} /> <PrivateRoute exact path="/" render={() => <>Main page</>} /> <Route render={() => <h1>Not found</h1>} /> </Switch>Using
PrivateRolesRouteroute componentCreate a new private route component that takes additional props and handles the conditional rendering of the protected route.
const PrivateRolesRoute = ({ roles = [], ...props }) => { const location = useLocation(); return isAuthenticated() ? ( <IsGranted for={roles}> <Route {...props} /> </IsGranted> ) : ( <Redirect to={{ pathname:'/auth', state: { from: location } }} /> ); };Use the new
PrivateRoleRouteand pass therolesprop.<Switch> <Route path="/auth" component={Auth} /> <PrivateRoleRoute path="/servers" component={ServerList} roles={['superuser']} /> <PrivateRoute exact path="/" render={() => <>Main page</>} /> <Route render={() => <h1>Not found</h1>} /> </Switch>
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
