react-route-guard v1.0.7
React Route Guard
Based on react-router-dom with route guard functionality on top of it. It makes it easier to control permissions and roles on a route.
Requirements
- Node <=7.1
Install
yarn add react-route-guardYou don't need to install @types/react-route-guard, as it's written in TypeScript and the npm module already has index.d.ts there.
Concept
SecureRoute<SecureRoute>works like same way as<Route> and therefore you can use it in the exact same way. To enable theRouteGuardfeatures pass in extra props (see below). SecureRoute will then perform the following tasks:No DOM component will be created before
<SecureRoute>has finished its checkWhen the
<SecureRoute>component has mounted, it will runRouteGuard.shouldRoute(). When this function is complete,SecureRoutewill update the state.RouteGuard.shouldRoute()can synchronously return abooleanor asynchronously return aPromise<boolean>orObservable<boolean>SecureRoute.render()will only render the route component when route guard has finished, and will render<Redirect to={this.props.redirectToPathWhenFail | '/'} />if the route guard check fails
<Router>
<Switch>
<SecureRoute path='/about' component={AboutComponent} />
<SecureRoute path='/users' component={UserListComponent} routeGuard={UserRouteGuard} redirectToPathWhenFail='/login' />
<SecureRoute path='/users/:id' component={UserEditComponent} routeGuard={UserRouteGuard} redirectToPathWhenFail='/login' />
<Route />
<Route />
</Switch>
</Router>RouteGuardRouteGuardis responsible for telling<SecureRoute>whether to enter that route or not.shouldRoute()is the key to making that happen, this method must return a boolean value in sync mode or return aPromise<boolean>orObservable<boolean>in async mode. Before theRouteGuardfinished, no child components will be rendered, which means no real routing will be executed.RouteGuardwill stop unauthorized users seeing a route altogether.
export type RouteGuardResultType = boolean | Promise<boolean> | Observable<boolean>
/**
* @export
* @interface RouteGuard
*/
export interface RouteGuard {
shouldRoute: () => RouteGuardResultType
}Usage
Here are some shouldRoute() example for different situation:
- Sync example
shouldRoute(): RouteGuardResultType {
// Sync API call here, and the final return value must be `map` to `boolean` if not
const resultFromSyncApiCall = true;
return resultFromSyncApiCall
}- Async Promise example
async shouldRoute(): RouteGuardResultType {
// You can use a `Promise` to get
// the final boolean result
return new Promise((resolve, reject) => {
// Simulate call backend API for checking the authentication and even authorization
setTimeout(() => {
resolve(true)
}, 3000)
});
}- Async Promise example with multiple promises
async shouldRoute(): RouteGuardResultType {
// You can use `Promise.all()` for getting
// the final boolean result to based on multiple requirements
const results = await Promise.all([
loginUserPromise,
getUserPermissionPromise,
getUserDashboardPromise
])
return results[0].user ? true : false
}- Async Observable example
shouldRoute(): RouteGuardResultType {
// If use Redux, we can dispatch an action to tell UI that `RouteGuard` is running.
// This is useful for displaying loading indicators before the `shouldRoute` is complete
appStore.dispatch(routingActions.executeRouteGuard())
// Simulate a call to an API to grab the user and permissions
return Observable.timer(1000)
.map(n => {
// When API call done, we need to `map` to `boolean` if not
return true
})
.take(1)
// If use Redux, then dispatch an action to tell UI that `RouteGuard` is done, can hide
// the loading spin right now.
.do(() => appStore.dispatch(routingActions.executeRouteGuardDone()))
}- Async real world example
shouldRoute(): RouteGuardResultType {
appStore.dispatch(routingActions.executeRouteGuard())
// Get the entire state to check whether the user is already logged in or not
const appState: AppState = appStore.getState() as AppState;
// Already logged in, pass instantly
if (appState.auth.loginUser && (!appState.auth.loginError || appState.auth.loginError === '')) {
// We can call any BackEnd APIs to rebuild the entire app state that are needed for the
// routed component to be rendered correctly
//
// Passed
return true
} else {
// Say for instance a user just opened a URL in a new tab, then we can try to load some
// cookie or call some API to load user info. If the API responds successfully,
// then the check has passed. From here we rebuild the entire
// app state for the routed component if needed
return UserService.loadUserStateFromCookie()
.switchMap(loadedUser => loadedUser ? UserService.loadUserList() : Observable.of(false))
.do(userList => {
// Dispatch an action to let Reducer rebuild the state synchronize
if (userList && typeof(userList) === 'object') {
appStore.dispatch(actions.rebuildState(userList)))
}
}
// Map to boolean result
.map(userList => userList ? true : false)
.take(1)
}
}