Routing for React
A very simple definition for routing could be loading components based on user demand. The consumers of your application surf through your app, and based on what they need you show them relevant screens. Routing is the ability for your application to find and load those components.
On a more technical level, based on user interactions with the app, a target URL is used to identity the related component which needs to be loaded, and React uses a switch mechanism to match the URL to the relevant component.
Routing could get a bit complicated when we want to sync the browser URL to with the loaded screen, when some routes need to be protected, or when the routing becomes nested. We will cover them all in this post.
By the way, this topic belongs to the series to set up a Single Page Application using React, Redux and Asp.NET 5.0.
- Styling and Theme Management
- Global Navigation
- Responsive Layout
- Forms and Input Validation
- Configuration Management
- API Call and Activity Indicator (Spinner)
- Monitoring, Logging and Instrumentation
- Toast Notification
- Redux and Store Setup
- Google Maps with Polygons
- SEO — Search Engine Optimisation
- Running React App on .NET Stack
- Deploy React App to Azure App Service
1. Root Router
Looking at an application at a high level, the below structure could be considered for an application, and it comes using the below hierarchy.
- Toaster: Pops up at the very top, so it makes sense if we keep it at top.
- Global Navigation: is always stick to the top, so again it makes sense to keep it there.
- Content: comes next, is dynamic and it loads based on user demand.
- Footer: Belongs to the bottom.
- Spinner: or the activity indicator, comes and goes based on the user activity like toaster, and it could be at the bottom
Based on the above structure, comes the below root router:
Root Router: is the one that takes care of the history of the routes as the user interacts with the system, and sits at the top of every other routes. Based on that, you can see I am injecting a history object to it, and that history is managed by a library called history. For more information on Responsive Page Layout, which expands on the above structure topic, refer to Responsive Layout Setup.
Here is the code on how to use the history library:
and to reference that history object, I just write:
import history from './common/history';
which will be fed to the Root Router, as you can see above.
Next, we get to our first route, which is /. As it doesn’t have have the exact attribute, it matches all the routes in the application, which means all screens will have the GlobalNavigation and the Footer.
2. App Router (Nested)
After root router, we have the app router, which is simply the content of each page that structurally sits between the GlobalNavigation and Footer. Within that, we can load the dynamic content users need.
Looking at what’s inside the AppRouter, you see a Nested Switch with a bunch of routes within the switch:
To improve the performance of my app, I am using Lazy to load the components on demand, and avoid jamming all the components in one giant app package, which would degrade my app performance.
2.1 Protected Routes
A protected route is the one that needs the user to be authenticated and prove their role before they can access it. Here is how it works:
- If the user is not authenticated and they want to access a ProtectedRoute, they should be redirected to the Login/SignUp page.
- If the user is authenticated and their plan doesn’t cover the feature they want to access, they need to redirected to the Plans page to upgrade their plan.
As you can see from the above behaviour, we are talking about two gateways (or questions to be answered) to give the user access to a certain premium features:
- Is the user authenticated to do that?
- Is the user authorised to do that?
2.1.1 Is the user authenticated?
When it comes to checking if the user is authenticated, it is as simple as checking a token in browser storage or cookie. If they exist, and it passes a certain set of criteria, you just say they are authenticated.
Configuration, plumbing and troubleshooting your software foundation take a considerable amount of time in your product development. Consider using Pellerex which is a complete foundation for your enterprise software products, providing source-included Identity and Payment functions across UI (React), API (.NET), Pipeline (Azure DevOps) and Infrastructure (Kubernetes).
2.1.2 Is the user authorised?
For authorisation, I am relying on a claim based identity model. Based on wikipedia, a claim is a statement that one subject, such as a person or organization, makes about itself or another subject. For example, the statement can be about a name, group, buying preference, ethnicity, privilege, association or capability.
A set of claims returned from your backend after the user is authenticated, can include any of the below:
- Date of Birth
Using the last item, Allowed Scopes, I basically say, each feature link (button) needs to have an attribute called RequiredScopes. On the other side, each authenticated user, will need to carry around a claim called AllowedScopes.
If the user’s claim, AllowedScope, contained all the RequiredScopes, that user is authorised to have access to that feature. Based on this, here is the code for ProtectedRoute:
Please note that I have simplified the code and how it reads user information. I am using Redux, but for simplicity I have removed it, and I just the user this.props.user.
Why Not Hiding the Protected Routes?
The other approach to protect your routes is to hide them in the first place, and only show them if the users have access to it. That’s fair. However as a SaaS application owner, you would need to be able to advertise premium features to your customers, and encourage them to upgrade their plans. To do that, they need to remember such features exist after their trial expired, and they need to be directed to upgrade their plans. If you hide those features, it’s highly likely they won’t upgrade.
How are you building your current software today? Build everything from scratch or use a foundation to save on development time, budget and resources? For an enterprise software MVP, which might take 8–12 months with a small team, you might indeed spend 6 months on your foundation. Things like Identity, Payment, Infrastructure, DevOps, etc. they all take time, while contributing not much to your actual product. These features are needed, but they are not your differentiators.
Pellerex does just that. It provides a foundation that save you a lot development time and effort at a fraction of the cost. It gives you source-included Identity, Payment, Infrastructure, and DevOps to build Web, Api and Mobile apps all-integrated and ready-to-go on day 1.
Check out Pellerex and talk to our team today to start building your next enterprise software fast.