Before talking about declarative routing, let’s have a look at the most common routing approach that many frameworks have implemented for many years.
What do you think the two following files have in common?
Both implemenations (.NET and PHP) are mapping some url/ path to a file that will run some code (AKA controller). Another thing they have in common is that all the routing is placed together and loaded before the app renders anything.
Having all the routing together breaks the co-location principle. Co-location is broadly accepted as a best practice by most of the React community. The idea of co-locating is that you should group things by concern, and not by implementation. Example, instead of having all the CSS in a folder, we co-locate the CSS with the components that are concerned about a specific CSS. Instead of placing all the GraphQL queries in the same folder, we co-locate them with the components that are concered about it. Therefore, the routing should not be an exception.
Other frameworks like Rails, Express, Ember, and Angular follow this same approach like the .NET and PHP examples shown above. Why would we group all the routes the same way?
Co-locating code makes it easier to mantain the code base of the application.
React Router before version 4 followed the same principle as the .NET and PHP implementations shown above. It kept the mapping between a path and a component in a separate file. Example:
Then you could import your routing file somewhere at the top of your component tree:
Imagine a Messenger app for authenticated users. The component tree could look like this in React Router < version 4:
This approach is called static routing, you declare your routes up front and then import them to the top-level.
Dynamic routing in React Router version 4
Considering the previous Messenger application, if we use a declarative routing like React Router version 4 then the component tree looks like this:
We are co-locating the route with the component that has to be rendered. This approach is called dynamic routing, it takes place as the app is rendering, not in a separate configuration file.
Real world example
Imagine you want to implement the routing for the following Messenger app. Notice that this page is a master-detail page, this means that when you select a given Thread with a user, that particular Conversation is displayed along with the Threads.
The following pink areas are the parts that will be common for the entire application, regardless of the url:
The path “/messages” willl dispaly the following Threads component:
When the user navigates to the path “/messages/:username”, it’ll display both the Threads component and the Conversation component of that given tread:
The component tree could look something like the following image. Given the url http://localhost:3000/messages/superuser23, the app will not render the Login component (considering the user is logged in). Instead, it will render both Threads and Conversation components.
If you want to successfully leverage React Router 4 and the declarative approach that React embraces, you should implement dynamic routing in React.
A common mistake we see in developers that are new to React, but experienced in other frameworks and tools, is that they try to bring the mental models they know even when those models don’t fit properly. In some cases (not always) those models are not the right way in React; routing is one of those cases. If you find yourself placing all your routes in the same place (e.g. a routing config file) in a React app, consider co-locating your Route components in the tree where it naturally falls into place.
This article is part of the training material of ReactJS.Academy.