Developer Insights: Dynamic Routing in React JS

blog-header image

If you’re a developer who frequently works with React JS, you have likely run into a situation where it becomes necessary to use dynamic routing.

Dynamic routing, with React, is a little like building a tunnel.

Most tunnels get you from point A to point B (which is like most applications in JS).

However, if we want to have cars and trucks exit from different areas, we’d create separate tunnels extending from point A. This time leading cars to Point B and Trucks to Point C.

In this way, dynamic routing lets you expand and add additional “tunnels” to your application flows. This means more efficiency (you don’t need to rebuild the tunnel entrance) and offers flexibility (if you want to add more tunnels down the road).

The following article will guide you through an overview of how to properly use dynamic routing and provide some helpful code examples.

Some Backstory

Recently, we had a client who wanted to add two additional products to their application (which we developed).

It was a single page application that could, at the time, only support a single set of routes from a single entry point.

With the amount of products increasing, there was a question of how we would accommodate two completely new products in the same application. Each product had their own unique flow, which needed to run through the single application.

In order to pull this off, we decided to create a dynamic router, based off the react router to help support our multi-flow application. In order to better understand this process, we’ve outlined the different stages and aspects of developing a multi-route application.

Getting Started with Dynamic Routing

We’ll focus on the type of dynamic routing that enables a router to react to information from an outside source – for example, information returned from an API or entered by a user.

In our case, we will react to information returned from an API. While business rules controlling the APIs may change, our routing logic would need little to no update. Although there is more to the configuration of our router up-front, it becomes easier to extend for larger applications.

Defining a Route

Every single route in our application is defined as a JSON object. Shown below in Figure 1, is an example of how one of our routes is defined.

You can see several key pieces of information here:

  • Top level key (name of our route)
  • Basic route information
  • Behavior related to this route
  • Basic analytics information
"userSelection": {
  "basicInfo": {
    "path": "/",
    "component": "user-selection",
    "translationKey": "user-selection"
  },
  "routing": {
    "restrictRoute": false,
    "ignoreTimeout": true
  },
  "analytics": {
    "stepName": "Welcome"
  }
}

Figure 1

Inside of the basic information, we define the path at which this route used, the component that is rendered at this route and a translation key. The translation key is used by i18n to serve the content for a component.

Route Behaviors

Following the route definition, route behavior needs to be set. We needed to solve how to restrict access to a page in the user’s flow, until the user is meant to see it.

We decided the best solution was to add a simple flag here. By doing this, we were able to check to see if the user is allowed visit this route when the route’s component is mounting. If they can navigate to this route, we can send them on their desired path. Otherwise, we restrict this route and provide error messaging.

In addition, we wanted to be able to time users out on pages where they could be entering private information, to ensure users’ security on public computers.

Analytics Information

Finally, we have a single piece of information that we needed for analytics.

This is where we defined the step name for a given route. The step name is a human readable name that is used for categorization and reporting purposes. We decided to place this information in the code as shown in Figure 2 below, since a step name should be tied to a route rather than a component. Other events are handled in our analytics reducer.

"analytics": {
  "stepName": "Welcome"
}

Figure 2

Requiring Routes for Products

A question that is commonly asked is, how do we know what routes to create for a product, and how do we create them?

We base everything off a URL parameter that relates to a product type. In our index file, when the site is loaded initially we capture this parameter and use a bit of string interpolation to require the correct routes.json, and the components that will be rendered at these routes, as shown in Figure 3 below.

var routeInfo = require(`./config/${applications[Config[product]]}/routes.json`);
var routes = [];
for(var route in routeInfo) {
  routes.forEach(route => route.basicInfo.component = require(`./includes/pages/${route.basicInfo.component}.jsx`).default);
}

Figure 3

With all of the relevant information in one place, we can start creating our routes. We have a function that returns a react route component containing the appropriate component for this route.

In Figure 4, you can see that we pull the path from the basicInfo property defined in our routes file and we pull the name of the component to be used at the time of rendering. We then pass through the rest of the props so we can access them inside our component.

const DynamicRoute = (route) => (
 <Route path={route.basicInfo.path} exact render={props => (
   <route.basicInfo.component {...props} {...route}/>
 )}/>
)

Figure 4

Rendering a Route

Now it’s time to render our routes.

NOTE: There are several other things happening here to wire up our redux stores, our browser history and our internationalization library, but we’ll save that for another article.

With all previous steps completed, from the array of routes we’ve defined below in Figure 5, we can use the map function to map each route to our dynamic route’s function.

<I18N appIdentifier={applications[product]}>
 <Provider store={store}>
  <ConnectedRouter history={history} ref={(router) => { this.router = router; }}>
    <App store={store} dispatch={store.dispatch} routes={routes} history={history}>
      {routes.map((route, i) => (
        <DynamicRoute key={i} queries={this.queries} {...route} store={store}/>
        ))}
    </App>
  </ConnectedRouter>
 </Provider>
</I18N>

Figure 5

Conclusion

After going through several iterations of routing, this provides us with the most flexibility. Not only can we update flows for a given product independently, but we can also define a new flow and products with relative ease by generating a JSON object.

The route shown in Figure 1 is a simple welcome page for our application. With this structure in place, we also have a convenient central place to define static information a route will need upon rendering.

By pulling together, our team discovered this approach for dynamic routing with React. This discovery was especially significant because we found a method that was flexible, organized, and scalable.

 

Looking for expertise in web development or analytics? Let’s talk and find out how we can help grow your business. Get in touch with us here.

 

About the Contributor

Web Development

Phil is a seasoned programmer who brings wizard-like knowledge when it comes to JS, HTML and CSS. When Phil isn’t delivering picture-perfect projects, he's putting pucks in the back of his opponents net during roller hockey games.