React Router to Redux First Router

Jon Samp
Codecademy Engineering

--

When I was introduced to React in 2015, countless blogs described it as the “View” in MVC. This notion of a new JavaScript framework without robust state management was crazy and unheard of, especially for an Angular 1.x developer like myself. I was interested in how fast and flexible React claimed to be, but it was unclear how to handle application data (the Model and Controller parts) of an app with React.

So, I never used React as a pure “View” technology. I was surprised by how well it managed application data out of the box. React’s APIs for state led React developers, like me, down a road of setting state and maintaining application data in a top-down fashion through a tree of components. I fetched data in a Main component and passed it down to each child component via props, making my apps a component tree.

Over time, this practice caused a lot of frustration and confusion. As my apps would grow in complexity, I would want to reuse components. Quickly, I found that my components were tied up in the context of their parent’s state data. If that wasn’t bad enough, changing the structure of any parent-level data meant refactoring many child components to match, which turned out to be a massive headache. My components described logic dealing with props more than they described presentational components.

Then Redux came along and changed the way I think about applications and how they work. Instead of keeping app data inside a tree of components, it’s maintained in a central data store outside of React entirely. Any React component can access or update the store, but none of the components need to know about or pass around the entirety of the store. It unlocked the ability to write React apps with genuinely reusable components at scale.

While Redux began to shift the tides of handling application data, nothing changed about routing in apps. React Router was (and still is) the primary way to create URLs and link around a React app.

A router in a React app holds routing and URL information, which is a type of application data. Interestingly, routing data is not stored in the Redux store when using React Router. Instead, it is stored in routing components spread throughout a React app’s components. If you want your Redux store to be the source of truth for your application data, you’re stuck syncing your React Router’s route to your Redux Store on every click.

As we transitioned to Redux at Codecademy, we began to feel the wear and tear of keeping our Redux store’s state in sync with our React Router’s route. We wrote a lot of code in componentDidMount across our apps to keep the Redux store in sync with the current route. Inevitably, these two sources of application data would get out of sync with each other and cause issues that were difficult to replicate and to fix.

Switching to a Redux router

For an internal application, we decided to use a Redux-based router to centralize our application data in our Redux store. After trying out Redux Little Router, we chose Redux First Router (you can read an in-depth comparison here).

Here are the highlights of Redux First Router:

  • All app data, including routing data, is in Redux.
  • Redux First Router dispatches an action on every route change, making it easy to fetch data with a thunk or saga on page load.
  • Debugging is easier when you have a record of all route actions alongside all actions.
  • Components do not need to know where they are nested, nor do they need to point to hard coded routes.

You can play with Redux First Router with this demo on Code Sandbox.

How it works

As the app loads, store/configureStore.js runs and sets up Redux First Router as the location node in the Redux store. Right after it loads, the store dispatches its first route action, which in the example above is 'HOME'.

That action sets an object in the Redux store with all routing information inside.

Getting around the app is facilitated by Link component. It comes from another package associated with Redux First Router aptly named redux-first-router-link. The Link component takes a to prop which requires you to specify an action. Inside index.js, you’ll see links like this:

<Link to={{ type: "ABOUT" }} ... >About</Link>

Upon clicking “About”, this Link would dispatch an actions and update the location node on the Redux store.

To render components from the Redux location object, we set up a scenes map in index.js. It’s a route-to-component map that allows us to take the location from Redux and render the corresponding component. Later on in the file, each component is loaded through the code below, where type is the route name:

{this.scenes[this.props.location.type]}

As you navigate through the example app, you’ll make it to the “Profile” page, which fetches fake user data on load. Inside routes/index.js, there’s code that calls an async thunk (a function that fetches fake user data) when the route changes. This project triggers a fake async function when the 'PROFILE' route is dispatched. To add a thunk to a route, Redux First Router allows you to define it in the route’s definition in routes/index.js, like so:

'PROFILE': { path: '/profile/:username', thunk: fetchUserData }

The code above tells Redux First Router to navigate to /profile/:username when a 'PROFILE' action is dispatched, and, to at the same time, to call the fetchUserData function. The fetchUserData function then fetches data before triggering an action and reducer to place the data in the Redux store. Using this pattern, we can navigate to a route and let Redux First Router handle the URL change and data fetching.

Wrapping up

Redux First Router provides routing data in Redux while keeping the URL in sync. This helps bring all application data into one place, which simplifies application logic. In addition, it does not require special routing components spread throughout our app, or passing routing data back and forth between a component and the Redux store.

The disadvantages of using this particular Redux router are that it’s a relatively new package created and maintained almost entirely by one person. In addition, the versions are not consistent with other packages. In the package’s readme, there are many mentions of a version named rudy which adds additional features and breaking changes. Typically, changes like these would be under a next flag.

Even with these concerns, Redux First Router gets what it does right and has made our app stronger. It lets us separate application logic from our components to let React act more like a presentation-only technology.

Interested in working for Codecademy? Check out our jobs page!

--

--