April 7, 2021

Coming to Terms with React Router

Bryan Travis Hooper

When I first learned about modern approaches to routing user requests to the appropriate resources I was learning Ruby on Rails and working under a RESTful paradigm. Next, I learned a bit about NodeJS and Express, which seemed to me to work in a very similar fashion. When I started learning React Router it seemed very different from those two, and I finally concluded that that's because it is!

First of all, let me explain what I think the purpose of a router is in a web application. A router is the collection of tools that interprets a request from a user, usually by parsing a URL, and directs the server to the controller or method that is designated to respond to that request. Usually, this results in a server response to the user that provides the requested resource. In a RESTful app, this would look something like this:

  • User requests in their browser.
  • Router has a GET route for /products that instructs the server to execute the #index method in the Products controller.
  • The Products controller retrieves all the products from the database, formats the data in the desired format (usually HTML or JSON) and returns the formatted data to the user. (More formally, the controller sends the data over to a view which handles the rendering, but nevertheless.)

But with React Router, there is no back end server at all, so everything is handled in the user's browser. Since it always seemed to me that the key feature of a router was the routing part, I found this a bit confusing. I found it useful for myself to think of React Router as more of a filter for components, that does its filtering based on the URL. React Router does more than this, but in terms of how it simulates a traditional Router, this is a key point. New components are shown based on a pattern matching algorithm that matches URLs and updates the browser's history to simulate page refreshes and different resources loading.

An artificial example will illustrate this point.

See the Pen React Router Example by Bryan Hooper (@bthooper) on CodePen.

If you examine this code, you will notice a list of links working more or less as a navigation menu. The links are created using React Router's <Link> component, which creates the <a> tag for the link. Each link points to a simple URL. Then, down in the App component, the <Route> components, also from React Router, direct traffic to render content depending on which route you are requesting. It seems simple enough, but React Router does some funny things with matching the URL and the paths. First of all, if you head over to codepen where you can play with this code, you will notice that every route you choose always renders the "/" route - namely the word "Home".

You might conclude that React Router is just matching whatever meets the minimum requirement to match the route. If it starts with "/", as all routes do, than the Home route will always match. You can see this behavior again when you click on More About. Here, the URL is "/about/more" and that matches "/" and "/about" as well as "/about/more."

But when you click on More Yet, things get a little fuzzy. The URL is "/about/moreyet" but only "/", "/about" and "about/moreyet" are matched. What wasn't "/about/more" matched? My guess is that React Router doesn't look at the URL as a string, but rather as a collection of strings separated by "/". So each string is comparaed aginst the other string within that segment enclosed by "/"s. The other somewhat unexpected behavior is demonstrated when you click on Blog. Two routes are pointing to "/blog" and so, when you click on that link you get both routes back! I guess it isn't that surprising, but one can see how it might produce unexpected results.

React Router provides a Switch component that can address some of these issues. Switch works by finding the first match and then only rendering the first match. However, it too can produce some surprising results if you aren't careful. Simply adding the Switch component around our routes will resutl in ONLY the home route displaying, since it is always matched. The "exact" keyword can be used to only match a URL exactly to overcome this problem.

In my mind it was helpful to think of React Router less as a routing tool that loads assets when requested by a user and more as a conditional rendering tool, that renders components based on matching the URL. Instead of a "router" I thought of it more as a conditional render tool. Routes are more like conditions or "if/then" checks that decided when to disply a component based on a given URL. Once I started thinking of React Router this way, it's behavior made much more sense to me and I was able to build more reliable React Router routes.

React Router is a powerful piece of software that does more than just "direct traffic." It also manipulates the browser history to maintain the appearance of a true router and preserve your URLs just like a real URL even though it is all rendered in the browser. I have only started scratching the surface of React Router, and I am eager to engage with it some more.