The only React Router set-up you will ever need

Marx Low
Marx Low
Jan 12 · 6 min read

This article aims to build out the 4 common routing needs to give you a rock-solid routing infrastructure for your React web application.

My promise at the end of your reading is a clean, scalable routing infrastructure that has the following features:

  • Adding layouts to pages
  • Nesting routes
  • Protecting routes, adding authorization
  • Passing props down to components

Overview

If you are working or have worked on the frontend, I am willing to bet that you have ever searched for any of the above routing features. I know I have.

A simple Google search can easily yield solutions to build these features. But in my experience, these solutions work well mostly in isolation and that’s not good enough.

The motivation behind this article is to unify the engineering of each feature. To create a powerful routing infrastructure that scales without conflict so you can focus on what truly matters on the frontend.

Centering that <div>

Let’s build something

To see the infrastructure in action, we will walk through building a simple React web application with it.

** Full source code here

There will only be 5 steps, each adding an extra feature on top of the previous.

I recommend taking a refresher of React Router’s <Route> component if you are unfamiliar at their official site here before we start.


1. Basic routing

We will start by building the routes for a simple Login and Dashboard page.

import { Router, Route, Switch, Redirect } from "react-router-dom";
import LoginPage from './pages/LoginPage';
import DashboardPage from './pages/DashboardPage';
<Router>
<Switch>
<Route exact path="/login" component={LoginPage}
<Route exact path="/dashboard" component={DashboardPage}
</Switch>
</Route>

There’s not too much going on here. As expected, any URL matching the path prop will render our component. For example, the “/login” URL will render our LoginPage component — Good!

Our Dashboard and Login page now has different layouts as seen below. And let’s say we want to add a new “/settings” page with the same layout as our Dashboard page (right). How should we do it?


2. Adding layouts

We can easily duplicate the code in our Dashboard component into a new Settings component. But this will not only violate the DRY (Do not Repeat Yourself) principle but its also very unscalable. Imagine duplicating the same code every time for a new page needing the layout.

A far better approach would be to make the layout reusable.

By refactoring the layout structure from DashboardPage into its own component, we can use it as a parent component to wrap all child components needing it like this:

(I named it AuthLayout.js as these pages will need authorization later)

With our layout component created, the next challenge is to find how and where exactly to wrap our pages with it.

I find that the best place to do this is to use the render prop in <Route/>. rendertakes in a function which runs and returns a component when our URL matches the string inpath. More about it here.

Pay attention to the lines 27–30 below here which wraps our pages (SettingPage and DashboardPage) with the AuthLayout component:

What you will notice:

  • We can easily control which layout to render for each page in ourpages constant. This means we can support multiple layouts without rewriting any code.
  • We are not using the component={} prop in Route to render our page component since we want to wrap them in our parent layout component.
  • We do not lose any power since we still have access to our route props (match, location, history)

Up till this point, we would have achieved a consistent layout in our pages (“/Settings” & “/Dashboard”).

But these pages are not protected, any user who is not logged in could still access them. What we want to do next is to redirect unauthenticated users back to our login page.


3. Protecting routes

Thankfully, we still have access to our route props (history) in AuthLayout.js. The solution becomes really simple from this realization.

We could turn our layout component into a gatekeeper and send any unauthorized users to the login page in our componentDidMount function as such:

The beauty of this implementation is not just in its’ simplicity but with how we have abstracted the responsibility of authorization from our page components away. Rather than checking for permissions in every single page component we only have to do it once at the layout level, DRY!


4. Nesting routes

Sufficiently large applications with many pages eventually reach a need to nest some of its’ routes. There are many ways to do this but we have to find one which can integrate smoothly into our current infrastructure.

Let’s examine how to nest our “/settings” page into “/dashboard”. The goal here is to render the settings page with the URL: “/dashboard/settings”.

Enter nesting <Switch> components — Since a <Route> “returns” a component when a URL matches its’ path, we can technically nest another <Switch> in the returned component to further navigate any URL with a “/dashboard/…” URL pattern.

What you will notice:

  • exact must be turned off at the first layer of <Switch> and is turned back on at the final (second) layer.
  • AuthLayout is still in play from the first layer. This means we don’t lose access control from earlier too and still get to keep our layout!
  • We have created a new component /dashboardPage/index.js responsible for routing any URL matching “/dashboard/*” pattern
  • We have refactored SettingsPage.js/dashboardPage/SettingsView.js
  • We have refactored DashboardPage.js/dashboardPage/DashboardView.js

5. Passing props to Components

This is a handy way of passing global or store-level data down to components whatever your use case is.

I personally use it so page components are spared from connecting to redux all the time. For example, rather than having both /dashboardPage/SettingsView.js and /dashboardPage/DashboardView.js connected to redux. I only have the parent,/dashboardPage/index.js connected to redux and pass global props down to these components.

Suppose we have a “user” variable that we would like to pass as a prop down to our DashboardView component.

const DashboardPage = ({ user }) => (
<Switch>
<Route exact path="/dashboard" component={DashboardView} />
</Switch>
);

We will create a higher-order component RouteWithProps.js (a function that takes in a component and returns a new component) to pass down what I call “extraProps” down to our components.

And here it is in action replacing <Route/> from earlier → <RouteWithProps/> to pass “user” down to DashboardView.js.

What you will notice:

  • render prop from <Route/> can also be used to pass in props, not just for adding layouts from earlier.
  • We have to pass exact & path to RouteWithPropswhich are <Route/> props so routing does not change.

Summary

** Full source code once again here

This routing infrastructure has served me well thus far for small to medium-sized applications. It isn’t perfect, and here are some of the bigger issues:

  • [Nested Routing] If you have a single “404” page beware that you have to explicitly write code to redirect at the second layer of routing again.
  • [Layout] Navigating to a new URL from “/Dashboard” → “/Settings” will re-render your layout again. I.E your layout’s componentDidMount function re-runs often and this can be potentially expensive if not handled well.

The perfect infrastructure does not exist in my opinion. A good engineer picks the right tool for the right job. I hope my writing has added one to your toolshed even if you don’t use it now.

I am constantly looking for ways to improve so let me know what are your thoughts. Thank you for reading!

JavaScript in Plain English

Learn the web's most important programming language.

Marx Low

Written by

Marx Low

Senior Software Engineer at Workmate

JavaScript in Plain English

Learn the web's most important programming language.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade