React Router Extended. How to make app routing easier while keeping it standardized.

Sergey Galuza
AIS Novations
4 min readDec 2, 2020

--

The main reason we focused on this topic was the lack of solutions for how to make app routing easier while keeping it standardized.

In our case, we required certain child routes nested under a single and simplified standard. When considering other projects, such functionality was implemented by different methods at the developer’s discretion but we did not find the one that would meet our needs. Besides, React Router DOM did not allow centralized implementation in the routing file. So, to understand which routes are accessible under the parent URL, we needed to enter the files that caused the inconvenience.

Angular had a properly written router, so we borrowed from them the basic concept — nested routes, guards, and resolvers.

Nested routing

To implement nested routing, we have created a library for React that conceptually is similar to the Routing in Angular. It will be available in React Router v.6.0.

Let’s consider an example of child routing:

 <ExtendedRouter
path="/parent-page"
component={ParentComponent}
childs={[
{
component: FirstChildComponent,
path: '/parent-page/first-child-component',
},
{
component: SecondChildComponent,
path: '/parent-page/second-child-component',
},
]}
/>

ExtendedRouter has a child prop, which accepts an array of child route objects.The component that was passed to component prop will have access to childRoutes prop, which is an analog of router-outlet from Angular. In other words, it allows rendering of child components and looks like this:

const ParentComponent = ({ childRoutes }) => {
return (
<div>
<h2>Parent page</h2>
<div className="children-content">{childRoutes}</div>
</div>
);
};

Guards

Guard is a convenient way to protect the page from the user who does not have sufficient access rights or is not authorized. It is needed for standardization and to facilitate more advantageous structuring so that, at the ‘very top’ of the files, you can see which permissions you require to enter the page.

Example of guard usage:

<ExtendedRouter
path="/app"
guards={[new LoginGuard()]}
redirectUrl="/"
component={AppComponent}
/>

This is a class that contains only one method canActivate that should return either true when the user is able to enter the page or false when one does not have access to enter the page. canActivate may be synchronous or asynchronous.

Guard and ExtendedRouter have the same property named redirectUrl. If the guard doesn’t have this field, it will try to take it from props in ExtendedRouter, if such exists, otherwise, it will redirect to ’/’.

In the example below, a simple guard checks if userInfo exists in localStorage. If yes, then the guard allows us to enter the page:

class FirstGuard implements Guard {
canActivate() {
if (localStorage.getItem('userInfo')) {
return true;
}
return false;
}
};

But, if the guard failed and has returned false, then we can handle it by using the redirectUrl field. In this case, we extend the example above with this field and specify to which URL we would like to redirect the user.

class FirstGuard implements Guard {
redirectUrl = '/login';
canActivate() {
if (localStorage.getItem('userInfo')) {
return true;
}
return false;
}
};

Specifying redirectUrl from the constructor is also possible.
One more important aspect of guards is that it has an array type, so when a user enters the page, all guards should be successfully completed. This allows mixing different guards for the same URL. The guards are called from left to right.

Resolvers
Resolver allows data loading after guards are passed but before the page is loaded. Also, it provides standardization and centralization of data loading. Resolvers, unlike guards, are called not sequentially, but in parallel so that others would not wait and do their job despite one slow resolver. Resolver is a class with only one resolve method that can be synchronous or asynchronous.

<ExtendedRouter
path="/app"
resolvers={{
userInfo: new UserInfoResolver()
}}
component={AppComponent}
/>

With the information sent to the component:

const AppComponent = ({ userInfo }) => {
return (
<div>
<h2>Parent page</h2>
<div>{userInfo}</div>
</div>
);
};

The key to the props in the component will be the name that was sent to object resolvers.

Finally

With this article, we wanted to show an example of a convenient solution for child routing thus making app development easier and keeping its routing standardized.

We have developed this library to resolve 3 main issues, which are inherent to many react apps: child/nested routing, Route protection, and providing data for component pages.

While there’s life, there’s learning. By that, we mean that your ideas and suggestions on our React Router Extended are highly welcomed and we would very much appreciate you giving it a try.

--

--