I'm trying to set up a Router
to use custom PrivateRoute
components that are equivalent to the example given in the official react-router
docs. The catch is that I also want to use a catch-all 404 route, so I think I need to use a Switch
component too, as shown in this example from the docs.
There's a conflict here because the docs for Switch
state that the children of a Switch
component have to be Route
components, and in this issue the team states that using non-route components inside a Switch
is unsupported, even if it happens to work.
I've created higher-order routes and am currently using them inside a Switch
and it seems to be working, but this won't pass code review at my company, since it's using an unsupported API that could break at any time. I want to know if there's a fully supported way to use higher-order routes together with a catch-all route.
I'm looking into trying to create a 404 route without needing to use a Switch
, or else using regular Route
components but wrapping the component passed to the Route
in the authentication logic instead.
import { Router, Route, Switch } from 'react-router-dom';
// Array of authorization functions to be tested by private routes
const authCriteria = [
// The next route must have been referred by a previous route
props => defined(props.location.state.from),
];
// Seems to work as expected, but violates the supported use
// of the Switch component
const AppRouter = () => {
return (
<Router>
<Switch>
<Route exact path='/' component={Store} />
<PrivateRoute
exact
path='/account'
component={Account}
authCriteria={authCriteria}
/>
<PrivateRoute
exact
path='/shipping'
component={Shipping}
authCriteria={authCriteria}
/>
<PrivateRoute
exact
path='/checkout'
component={Checkout}
authCriteria={authCriteria}
/>
// Catch-all route means we need to use a Switch
<Route render={() => (<h2>Not Found</h2>)} />
</Switch>
</Router>
);
};
export default AppRouter;
Based on your question's links to react-router's github, it doesn't appear like you can use a HOC inside a Switch
.
However, you can achieve something similar. Basically, you need two routers. The first Router
routes all pre-authenticated routes, like logging in, new users, password forgot, etc. Since it's inside a Switch
, it should render your second Router
. Your second Router
will contain all your authentication required routes. However, you need to include a conditional check to make sure the user is authenticated. If the user isn't authenticated, you can just return a Redirect
back to the /login
path. This works because of the first Switch
. If the user goes to a url that isn't available to unauthenticated users, the user will always get redirected to the /login
since they will hit the default condition in the first switch and the Redirect
of the second component.
import { Router, Route, Switch } from 'react-router-dom';
const AppRouter = (props) => {
return (
<Router>
<Switch>
<Route exact path='/login' component={Login} />
<Route exact path='/new-user' component={CreateAccount} />
<Route render={props => <AuthenticationRouter {...props} />}
</Switch>
</Router>
);
};
const AuthenticatedRouter = (props) => {
if( props.userIsAuthenticated ) {
return (
<Router>
<Switch>
<Route exact path='/' component={Store} />
<Route
exact
path='/account'
component={Account}
/>
<Route
exact
path='/shipping'
component={Shipping}
/>
<Route
exact
path='/checkout'
component={Checkout}
/>
<Route render={() => (<h2>Not Found</h2>)} />
</Switch>
</Router>
);
}
return (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
);
};
export default AppRouter;
Update: I changed my route authentication HoC to wrap the component rendered by the route, not the route itself. The first answer that was posted would probably be better if the authentication criteria were static, but the app I'm working on is an order placement workflow where access to different routes can depend on different factors, like having completed or come from other routes, so I needed to be able to pass arbitrary authentication criteria to each route.
Here's the code that shows the withAuth
HoC I used to wrap each component and how it works with the router:
// Test every passed-in auth verification function.
const verifyAuth = (authCriteria, props) => {
if (authCriteria.length === 0) return true;
return authCriteria.every(criterion => criterion(props));
};
// Authentication HoC
const withAuth = ({
authCriteria = [],
redirectPath = '/',
} = {}) => Component => props => {
const isAuthorized = verifyAuth(authCriteria, props);
return (
isAuthorized ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: redirectPath,
state: { from: props.location },
}}
/>
)
);
};
// TODO: authenticate user
const validUser = _props => true;
// The next route must have been referred by a previous route
const internalReferral = props => defined(props.location.state);
// The Store route has different authentication requirements
// than the other two routes
const storeCriteria = [validUser];
const mainCriteria = [validUser, internalReferral];
const authRoute = withAuth({ authCriteria: mainCriteria });
const ProtectedRoutes = {
Store: withAuth({ authCriteria: storeCriteria })(Store),
Shipping: authRoute(Shipping),
Checkout: authRoute(Checkout),
};
const AppRouter = () => {
return (
<Router>
<Switch>
<Route exact path='/' component={ProtectedRoutes.Store} />
<Route exact path='/shipping' component={ProtectedRoutes.Shipping} />
<Route exact path='/checkout' component={ProtectedRoutes.Checkout} />
<Route render={() => (<h2>Not Found</h2>)} />
</Switch>
</Router>
);
};
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.