A Navigation-Enabled Redux Reach Transition Router

Ajay Suresh, CC BY 2.0

These days I can’t browse the web without running into an ad for those smart pressure cookers — they cook rice perfectly, they slow cook stew, you can even make a cake in one, which we did, and it was tasty although it was clearly a steamed cake, kind of like Mushi-Pan.

Frédérique Voisin-Demery, CC BY-SA 4.0

In this article I’ll present something similarly full-featured: a React routing component that works with Redux, Reach-Router, and React Transition Groups, that also works seamlessly with browser navigation.

I’ll provide some concise code examples where it helps tell the story, but for the full code, visit the related github repository. The project is developed iteratively in tandem with the article, and links provided below should refer to the application code at that stage of its development. For the completed version (spoiler alert), please refer to the repository’s master branch.

Thanks go to all those participating in the discussion at @reach/router/issues/156, and, most of all Salvatore Ravidà whose excellent npm package is a dependency of this routing component. That library has its own demo project, which definitely proves its flexibility and clearly illustrates its use. The application routing component presented in this article is more focused in regard to its routing code (one approach is presented while many others are possible) while also working with React Transition Groups in a single component, demonstrating one design to accommodate animated view transitions, such as slide-in/slide-out.

My own path to needing this component began with Redux — I already had an app whose state was entirely in the Redux store, and that was working perfectly. Catching up to that point, version one of the example app has Redux with a simple state for a very simple single-page app:

Next up we need a second view to route to, which is added in version 2 of the example project.

Suppose that we’d like to allow the user to bookmark and navigate to these views directly. So we’d like the app to interpret the and choose the view as a function of the URL in the browser’s location bar. This raises a question: should the be the source of truth for the view selection, or would we like the app to be driven from Redux? Either choice is better than being unclear about it, because to keep the app simple and reliable, we’d like to ensure that there is a single source of truth for the inputs to its rendering. That doesn’t necessarily imply that all the state has to go in Redux (although that does seem like a good idea), but for this one bit of data, , its single source of truth, from the perspective of the application code, could be Redux or but hopefully not both.

Let’s get an idea how this looks by adding the most basic navigation-enabled routing to the app. is added to the package dependencies, providing the hook. This requires that the application is placed within a element.

App.js: rudimentary window.location-based view routing

There are more sophisticated ways to use @reach/router (all of them), but we’ll endeavor to use its interface narrowly to keep things simple.

To finish up with version 2, the “view-routing logic” aspects of the previous code sample are abstracted into our routing component, that for background reasons we will call the . It’s a less descriptive, but more compact, name than the alternative, .

MushipanRouter.js version 0.0.0

Which allows the component to be simplified, in contrast to the previous version:

App.js: now with declarative view routing

So far we’ve got some extremely basic routing logic, factored into and implemented using , but the Redux application state and the view routing are completely separate inputs to the app rendering.

For version 3 of the example project, we’ll consolidate these 2 inputs under Redux, which should help to streamline application logic, which in practice we’d anticipate has been developed largely against the Redux store.

The key piece is the npm library. In order to put it in place, it requires changes in the middle of the application store and reducers initialization, which may, depending on the application’s maturity, feel somewhat like brain surgery.

The root reducer’s default () export is now , that takes a reducer as parameter and invokes on all the pre-existing reducers as well as the newly-introduced reducer.

createRootReducer(routerReducer)

The store setup now involves using from npm package , that’s supplied as parameter to from , resulting in the . We also use another result from that step, , to arrive at a (“reachified”) , ultimately supplied as a parameter to the .

store.js updated: create the location history using Redux and the browser history

Let’s not overlook that the rendering is, as of version 2 presented earlier, a function of , provided by . It works, but it doesn’t follow the single-source-of-truth we’ve attempted to engender in the changes to the store and reducers above. Now it can be updated to obtain the same input using Redux instead.

MushipanRouter update: uses history and location.pathname from the Redux store

Next let’s try to simplify the changes to by moving some of the boilerplate into a component associated with . For this we’ll reorganize it from a single component into a small subpackage:

└ router
├ MushipanRouter.js # our component
├ index.js # declare exports
└ history.js # some of the boilerplate

How far did we get? is not beautiful but is cleaned up a bit:

store.js after moving some of the boilerplate back out (compare versus above)

The way is intended to be used, we have to obtain its reducer (1) before creating the store, where we pass it as a parameter (2), and then using the , initialize a static (3) that is integrated with both the and Redux. Perhaps it isn’t perfectly factored, or for some reason it can’t be, since as is, the history initialization is necessarily interwoven with the initialization.

Final feature for version 3, to complete the Redux-driven routing aspect of the example project, we’ll provide some buttons that use Redux to change the view selection.

These buttons are not good UX for anything. They’re serving as a mock-up of any source of Redux actions — all they do is fire off a view-changing action.

To get them working, at a minimum, we need to supply the when calling :

store.js: now also using the router middleware

And then we can dispatch these actions, like so:

At this point we can verify on testing that browser navigation (i.e. Back and Forward buttons) seem to be totally compatible with using these “view changing buttons” — this should be the basic ingredient for a gratifying UX, although perhaps not with these buttons per se!

Hopefully it goes without saying, that since our new is just a mock-up for the example, what we really need is to factor out the “mock up,” from the useful mechanics and move the mechanics into the . The routes and are also part of the example app — let’s coalesce what’s needed into declarative set-up code and make it a bit more dynamic in :

useDispatchRoutes — a new export from the router subpackage

Now we can update to illustrate how can help to drive a declarative DRY style in the App:

ViewChangingButtons.jsx: part of the config-driven logic, on the application side (bad-UX mock)

And finally for part 3, the code:

Obviously we could have imported from the component, but importing it once here and supplying it to both side-by-side illustrates the idea for simplifying the router API in that it accepts the same declarative config in different cases.

There are better ways to set up UI controls for routing, for example, see the documentation. And routing actions could also stem from other application logic, for example timing out a user login session.

Following along with part 4 of the example project, next we’ll add . As they say it, is a state machine for managing the mounting and unmounting of components over time.

A view router could be described as a component that selects a single view to display based on some application state.

There’s obviously some overlap between what the view router and the are supposed to be doing. That just makes it important to coordinate them with each other, and, helpful to simplifying other app code to coalesce them into a single unit within the app.

Our aim is to simplify the view routing conceptually, as it is used by the rest of the App. Having the router manage the , we arrive at a single “actor” during the timeline:

Fortunately, there’s an excellent example for integrating with transition groups at their documentation site. The approach in that example is used here with only minor adjustment to fit into the example project’s development thus far.

I picked JSS to define the transition styles in the example app. Any approach to defining style classes in React will work just as well — nothing about MushipanRouter requires JSS. (Saving for another time a complete pros-and-cons rundown of the ways to supply styles to a React app.)

define an object with style classes named accordingly

There’s another way to do it, represented in more of the existing examples, using a string prefix for the classes, however the “object-oriented” approach seems cleaner somehow.

We supply this group of CSS transition styles to the :

supply a transition styling parameter to MushipanRouter

And then set up the components in :

🎉 Et voilà!!

Here we can see that the state is persisting, when the user name is entered, it’s seen again, even when going back via the browser’s button. Weird stuff! Also it looks like we could use a . Using this stack it’s no problem, following on examples in , and bearing in mind that our location state (including whether we’re pushing or popping the history) is available via the part of the Redux store, allowing us to pick the right set of classes and keep the rendering a function of the Redux state!

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store