How Quorum Tamed React Router

Will Deuschle
Qbits
Published in
6 min readJun 2, 2017

As an engineer, Quorum’s switch to a single-page React-based application was joyful for many reasons. Declarative style? Check. Reusable codebase? Check? Test-driven development? Check, check, check.

Ignoring the inner geek of the development team for a moment, this transition also heavily benefitted our user base. Navigating between views in Quorum became snappy practically overnight! Zooming around the site with minimal transition time enhances user experience, reduces cognitive load, and ultimately creates a more valuable product.

speedy

These seamless transitions were made possible to a great extent by the fabulous React Router package. Stitching our site together wasn’t just easy — it felt trivial.

More or less, this was all we had to do:

To connect our views together, we simply replaced <a href/> elements with <Link/> elements.

So, where are we going? Was everything this simple? Pat on the back for the React Router team?

Not quite.

There were a few difficulties with this new structure that we needed to figure out — things that most nontrivial single-page application (SPAs) will need to deal with at some point.

Namely, what if we required complex routing logic that wasn’t as simple as a basic link? And what if our users wanted to intelligently jump back and forth between views? Let’s address these two issues one at a time.

Complex Transitions

There are a few use cases where we wish to enable transitions that aren’t as simple as clicking a link to a different part of our site.

For example, we might want to redirect users to a list of data sorted and filtered by earlier actions, or more generally modify a future view based on a previous view. In other words, we had dynamic transitions.

React Router is great for a lot of things. Dynamic transitions are not one of them.

It wasn’t just that we need the url to be different; we actually wanted to modify the internal state of our application in addition to redirecting the user.

Now, we see why this might rub some people the wrong way. In a traditional application connected together by links that render new templates on each page load, this would be sacrilegious. Any information passed from one view to the other would need to go in the url to be parsed by the application prior to rendering. A url might look something like:

www.quorum.us/place/?user=ferris&favorite_activity=sleep&friendliness_score=11&current_best_friend=cameron&sickness_level=null

Yuck.

We don’t even want to read that, let alone parse and inspect it for our application logic.

This is a single-page application and we’re using Redux; can’t we just dispatch an action to update our application state? Add an onClick handler, perform our state updates, redirect the user, and move on with life?

For an application like Quorum, which is heavily influenced by user actions and preferences, we needed to do this in a lot of places, which left us a little uneasy.

Moreover, it degraded a common functionality of the browser that users justifiably expect: right click to open a new tab.

Our users definitely wanted to open multiple tabs. Quorum, which has millions of documents, bills, votes, amendments, etc., is an application that benefits from opening several tabs of related content at once. For example, I might want to open up every bill profile related to environmental regulations passed in the last year. Or see the member profile of every nay voter on an amendment I care about. Or… you get the idea.

A bill profile

By attaching onClick handlers to <div/> elements instead of using standard <a href/> tags (or <Link/> elements, in the case of React Router), we would have settled for an inferior solution and left some of our users unhappy.

Enter the SegueLink

We decided we could do better, and in fact, needed to do better. Our solution was to write something we call the <SegueLink/>, which takes advantage of the combined power of Redux and React Router.

The <SegueLink/> is a wrapper around a standard <Link/> element, except instead of just rendering an <a href/>, it also allows us to execute arbitrary functions prior to said transition. Bingo.

The best part was that this only took a few lines of code. Here is what a simple implementation might look like:

We are able to intercept the LOCATION_CHANGE action from any transition, which is dispatched thanks to the react-router-redux package.

From there, we have access to the flag and payload specified by the <SegueLink/> (they are conveniently attached to the LOCATION_CHANGE action), allowing us to perform whatever state updates we deem fit.

With that we had solved the first hurdle of a SPA using React Router: custom transition logic that retains expected browser functionality.

Intelligent View Navigation

Our second issue was related to allowing our users to quickly and easily jump back and forth between views.

I can hear the internet screaming: “what about the browser’s bloody back button???”.

I acknowledge that the back button is useful. Please forgive me.

Our main beef is that it’s a little too simple for some things.

To illustrate, a user’s interaction with an application might look like this:

linear

But more often, for sufficiently complex applications, the interactions actually look a lot more like this:

web

Nodes V1 and V2 above are what we consider “dead ends”. This might be something like an account page or a “thanks for doing xyz!” page, but it could be any view depending on the application. The point is that after leaving, the user probably wouldn’t want to return. It’s a one-and-done type situation.

We wanted to offer our users a way to move back in time in their browsing session. We could have messed with their browser’s history to accomplish this, but that is a source of truth we don’t want to undermine in most situations.

The solution was this fun little widget, that appears in the top right corner of our site:

We hoped it would be landmark for users, and from feedback received, we think it is. Click a wrong button? Decide you want to jump back to exactly what you were doing? Enjoy playing with random functionality? Click the ‘X’.

Under the hood, all we had to do was build up a simple stack containing the previous locations of our users and relevant information for each of those particular views. To make browsing sessions more intuitive, we are selective about what things get put on that stack. The visual cue of the ‘X’ lets our users know when they can pop back.

Adding views to the stack means executing a function on each transition, which requires — you guessed it — a <SegueLink/>.

Wrapping Up

In sum, we’ve enabled complex transitions with our <SegueLink/> component and provided our users with an opinionated navigation tool through the ‘X’ mentioned above. Adding these two layers of functionality on top of vanilla React Router creates a more convenient, easy-to-use, friendly experience for our users. The package has so many strengths that using it was a no-brainer. But, like with most things, it didn’t quite do everything we needed. We souped it up a little to fit our needs.

At Quorum, we rely heavily on open source tools but aren’t afraid to dig in and make something better ourselves. Interested in what we’re doing? We’re hiring.

--

--