Let the URL do the Talking, Part 2: Bargaining and Acceptance with Redux and React Router
In part one of this series, I explored the pains of integrating React Router into purely-functional Redux applications. There, I discovered that not only does React Router dictate an architectural coupling between your state and view layers, but also renders a set of powerful URL-driven Redux patterns impossible.
The root cause of these problems is that we can’t lift URL state from React Router into a Redux store. Without this capability, rich URL data can’t participate in critical application decisions. The URL is a silent observer when it could be a primary source of truth.
We want the URL to do the talking.
The open source community isn’t blind to the problems of using React Router with Redux, and a set of popular libraries provides couple’s therapy for the two systems. As helpful as that sounds, I worried about their utility after deep-diving on the fundamental problems of integrating Redux and React Router. I needed to ask: do these integration libraries address the root issue of lifting URL state into the store?
No, and sort of.
Acceptance
react-router-redux
(formerly redux-simple-router
) is the more popular of the two integrations, advertising itself as “ruthlessly simple bindings to keep react-router
and redux
in sync.” It coordinates state sync between the two libraries when using time travel in Redux DevTools. Great, one big integration problem solved! Let’s find out how to read our URL state from the Redux store.
Just kidding! You can’t.
From the README
: “You should not read the location state directly from the Redux store. This is because React Router operates asynchronously (to handle things such as dynamically-loaded components) and your component tree may not yet be updated in sync with your Redux state. You should rely on the props passed by React Router, as they are only updated after it has processed all asynchronous code.”
URL state is in the store, but you can’t use it. Confused? You’re not the only one.
In response to a GitHub issue with similar concerns, maintainer Tim Dorr writes: “Don’t use this library to connect Redux middleware and reducers to the Router. There are numerous problems with trying to use that (mostly centered around it being impossible to serialize the router’s state) and not the intended purposes of this library. [Its] sole purpose is to keep track of locations for developer-only features like time travel.”
react-router-redux
accepts that lifting URL state from React Router is a tough problem, and therefore doesn’t try (or claim) to solve it. While your DevTools work again, you’re still stuck with container components and a tangled architecture.
Bargaining
redux-router
is the old guard of the two integration libraries, and it comes closest to solving the core problem of lifting React Router’s URL state into Redux. Not only does redux-router
sync the URL, params, query objects, etc. to the store, but it also guarantees that these values are valid and safe to read, unlike react-router-redux
.
Routing stays in sync with Redux dev tools. Selectors that inspect URL state can live above connect()
. All is well.
redux-router
’s noble goal comes at a price. The library author, Andrew Clark, best explains the limitations of redux-router
:
“The [React Router] data is not all serializable (because Components and functions are not directly serializable) and therefore this can cause issues with some devTools extensions and libraries that help in saving the store to the browser session. This can be mitigated if the libraries offer ways to ignore serializing parts of the store but is not always possible.
“redux-router
takes advantage of the RouterContext to still use much of React Router’s internal logic. However, redux-router must still implement many things that React Router already does on its own and can cause delays in upgrade paths.
“redux-router must provide a slightly different top level API (due to 2) even if the Route logic/matching is identical.”
These limitations cause enough concern to both the maintainers and the community that the README
not only brands the library as “experimental”, but also recommends react-router-redux
. Since react-router-redux
doesn’t solve the same problem, following this recommendation sticks us into a loop.
redux-router
is reaching the end of the bargaining stage as it realizes that negotiating a fragile peace with React Router isn’t sustainable.
However, if you’re 100% stuck with React Router, need to read URL state in Redux, and can handle some instability and uncertainty, use redux-router
, since it focuses on the important problem of lifting URL state into the store.
Redux-First Routing
After discovering the problems of the two integration libraries, we decided that routing in Redux needed a fresh start. In part 3, we’ll learn about redux-little-router
, how it operates from the top (Redux) and not the middle (React), and the useful tools it provides to route flexibly without eating your existing architecture.