Redux-First Router data-fetching: solving the 80% use case for async Middleware

James Gillmore
Reactlandia
Published in
26 min readJul 19, 2017

--

your routesMap configuration — notice the thunk option

Understanding the architectural decisions behind the tools you are using is perhaps more important than the many things a new package does for you.

The reason is because once we reach a certain skill level in our journey, we become aware of the many ways to do the same things, and expend too much time wondering if we’re doing things the best way. That’s what Javascript fatigue has been all about.

The goal is to get past the point of second-guessing ourselves and to become confident in the path we've chosen so we can execute quickly and efficiently.

But that doesn’t mean we shouldn’t listen to our gut instinct. Sometimes we know deep down, even though we are using the fanciest, say, async middleware — after having tried them all — we still aren’t doing it the “correct way.”

The only way to truly stop second-guessing ourselves is do things the “best way.” As unhelpful as that sounds. When the signals inside yourself stop, you know you’ve selected the correct strategy for this juncture in your journey. This isn’t an uncommon occurrence for the persistent developer — and when we have the luxury we should always pursue it.

THE 80% USE CASE FOR MIDDLEWARE

Today I’m going to explain why Redux-First Router’s data-fetching strategy is a sound and powerful strategy for the 80% use case, which in terms of your app may very well be 100% of what you need.

The 80% use-case for async middleware is dispatching 2 actions in sequence: the synchronous “setup action” ™ and an asynchronous “follow-up action” ™.

Before we start, I want to plant a concept in your mind so you have a frame of reference — there are 2 primary data-fetching strategies for React:

  • Within components (“component-paired data dependencies”)
  • Triggered by routes (“route-paired data dependencies”)

Async middleware falls in the second category — just without URL-ized actions™ unless you’re using Redux-First Router.

NOTE: Apollo and GraphQL specializes in the former category. The purpose of this article isn’t to debunk that as an effective route. I recommend Apollo on larger apps. Redux-First Router works great with Apollo. Perfectly in fact. You just use less of Redux-First Router’s data-fetching capabilities. So it isn’t a Redux-First Router vs Apollo thing. But Apollo’s capabilities will serve as another useful frame of reference throughout this article.

What it is though, is an ad-hoc componentDidMount fetching thing vs. RFR’s route-paired data fetching thing. We will tackle the problems with componentDidMount before getting to why current middleware strategies are too complex and miss the mark for the 80% use case.

COMPONENT-PAIRED vs. ROUTE-PAIRED DATA DEPENDENCIES

Pairing data to components makes a whole lot of sense when you have a system like Apollo that handles all the nuances for you (particularly promise resolution on the server). Without it, not so much.

There‘s a hype train around pairing even non-GraphQL data to components via componentDidMount.

In the majority of cases data can be paired to the route, i.e. the URL and parameters in the URL. How to get a “handle” on the route will unveil itself later in the article. Here are the reasons why route-paired data deps are preferable:

  • easier testing (components don’t need data-fetching mocked)
  • server-side rendering becomes a dream (just fetch data based on URL params, pre-populate your Redux store, then call renderToString once)
  • perf is better on the server because you don’t have to render your component tree twice (1st while resolving promises like Apollo does)
  • marshaling data from the server to the client is just a matter of assigning it to window and rehydrating
  • you avoid latency from nested components fetching additional data when you were already able to fetch all the data you need based on the route
  • you circumvent the fact that Redux and react-redux can’t reliably dispatch within componentDidMount on the server
  • you don’t have to write custom error-prone stuff to call componentDidMount on components that will likely be rendered

Those are things you want. Those are things that make your life far easier. They can be summarized as:

  • SETUP STATE FROM THE URL
  • then RENDER FROM STATE.

WHEN DO YOU NEED COMPONENT-PAIRED DATA?

So when do you really need component-paired data? That’s the question you must ask yourself to make the best decision here.

Well, let’s talk about Apollo for a second. And the reason I don’t mention Relay is because Apollo works with Redux and since UI-state is so important — I want something made for Redux. The story is over in my mind. However, when I say “Apollo” I really mean Apollo or Relay or similar.

Not all apps need GraphQL and Apollo. In fact, I don’t believe the hype that GraphQL is a REST killer, even though I’m a big supporter of GraphQL + Apollo. I simply recommend the latter for greenfield apps that will inevitably become large. Or perhaps after the prototyping phase of your app.

So what do you get from “Apollo”?

  • you get data that is that is in close proximity to, and pre-shaped to the shape of the props your components receive (“co-located”)
  • You get to avoid any friction around having to manually fetch the data in componentDidMount.
  • after initial setup, you have far less thinking to do regarding sending the data from the server.
  • you don’t get caught up in re-making ad-hoc fetching/sending schemes; no one-off endpoints that you incrementally add and which take you away from the “intuitive” mindset of development

You’re basically distilling what otherwise is a never-ending boilerplate creation process into the purest definition of what you need, as you need it. That’s great power! That makes the bulleted pros of route-paired data deps worth going without.

That’s the only time I would recommend not fetching data in a way paired to your routes. I.e. when you get all these extra perks, and a fantastic feature-complete library to bring it all full circle. Go Apollo!

AD-HOC FETCHING IN COMPONENTDIDMOUNT

Now for the rest of us: if you’ve gotten attached to fetching data in componentDidMount(and you’re not using Apollo), you’re doing it “wrong.” Unless you have these needs:

  • you can’t change the primary API endpoints that serve data, but you can create new endpoints; so it’s helpful/required to guarantee components for new features have all the data they depend on. Huge companies with many developers working on big apps (ahem, Facebook) have this problem. It’s the reason Relay was created. Very few apps fall into this category — it’s the token trap of added complexity for features you don’t in fact need. (unavoidable reason)
  • you have a component that renders in several different “routes,” and you don’t want to have to change multiple routes to insure it gets what it needs; it’s easier (reads: you’re feeling lazy) to write the custom logic at the component level, even if it means an additional fetch. (bad reason, especially if you’re server-rendering)
  • Your components are derived from UI state that that has no corresponding URL. Solving this one is what Redux-First Router is all about. Any action can now be URL-ized. It’s an excuse if you haven’t URL-ized most of your actions. (bad reason, but only really possible thanks to RFR)
  • you’ve come up with some concoction where props cascading through your component tree generate parameters that exist only in the View Layer (aka your component tree). If you have this problem, you’re likely doing something wrong and need to get better use out of Redux. Or somehow you’ve dug yourself into this hole after months of development and there’s no going back. (bad reason / unavoidable reason)

Moving state out of the View Layer into its designated location (Redux state, aka “fat models”) and moving data-fetching to its designated location (routes, aka “thin controllers”) is the time-honored tradition of MVC we’ve all been through as we refine our processes. It keeps popping up because it works.

Fetching data in componentDidMount is the modern incarnation of the fat views/controllers anti-pattern.

The problem has been we have had no good designated place for “controllers” …until now.

So bottom line is all the reasons above are either bad reasons or unavoidable reasons you can’t control. If it’s the former, the examples soon to come will help. If you’re in the latter case, I feel for you. I suspect it’s because you’re in a large organization with serious apps and old APIs. Maybe it’s time to make a push for an Apollo/GraphQL overhaul.

I don’t propose to have solutions for everyone. My goal is first to help you see the difference between route-paired vs component-paired data dependencies and see if perhaps you could have done it via the URL all along.

ROUTE-PAIRED DATA FETCHING

To be able to accomplish route-paired data fetching, you have to ask yourself one question:

Is all the information I need in the URL or cookies?

Keep in mind request headers only apply to ajax requests, as you can’t control the headers sent when users visit your website directly. That point is more important than ever now that universal code-split apps is a solved problem.

The day’s of single page apps solely making AJAX requests is over.

ASIDE: UNIVERSAL SPLIT RENDERING > SPA

You “shouldn’t” be making your app as an SPA if you’re a “power user.” You’re losing customers from google. And the time till interactive (TTI) is slower, which not only bounces a higher percentage of your visitors but compounds the google problem since google likes fast sites, etc.

In general, if you’re not server rendering your app and simultaneously code splitting you’re losing users/customers/money. The only reason ever to make the tradeoff between one or the other was because the development costs of trying to do both were too high. And guess what? They were. I’ve spent like 6 months on that problem alone so you don’t have to :)

BACK TO BUSINESS

Ok, let’s get back to business: you have the URL + cookies. Cookies means you have your user. So let’s distill what you have down to URL parameters (via path or query) and the knowledge of which user is visiting your site. Those 2 things. That’s it.

So the question is (since we are diligent developers who refuse to have blind spots): WHAT ARE WE MISSING?

The reality is for greenfield apps which can control what URLs they’re using, you’re not missing anything. Put every bit of state that otherwise would only exist in the component tree or your redux store in the URL. That is: every bit of critical state from which everything else can be derived. Story over.

You only want to do the former when you truly have to. The primary reason component-paired data is “bad” is because you don’t have Apollo at your service on the server recursively resolving promises attached to components.

Calling await SomeComponent.prototype.componentDidMount() or await SomeComponent.fetchData()— and making the determination of which component to call it on — is not professional in my opinion. You basically end up with a custom system that only exists in your app, when a general abstraction serves newcomers looking at your code far better. Redux-First Router is that abstraction by the way, and it skips the componentDidMount thing altogether by moving things closer to the URL, which leaves you with more pure components + less coupling in the View layer.

Keep in mind with all this, the problem primarily exists server-side. The nature of handling requests on the server is synchronous. Regardless of whether you use async/await, a request is handled by you sequentially fulfilling a to-do list.

On the client where you have a live “breathing” app responding to user inputs/events, child components within your tree can respond individually. It’s truly asynchronous. In this case, component-paired data fetching is a nice impedance match. Again, Go Apollo!

So on the server you want to respond to incoming requests as quickly as possible. And to do so, it must be in a way that makes use of the state initially supplied: the URL. That’s where the sequential/synchronous thing plays out.

Rendering twice — if only in the virtual DOM one of those times — is precious cycles one day you will wish you conserved. If it results in multiple non-parallel requests for data, even worse. Nobody’s saying don’t use Apollo. Use it in places other than your high-trafficked public portions of your site.

Bottom line: Apollo lends itself to a nice workflow, but not one you want to get carried away with when it comes to server-rendering, or you’ll have longer response times. It’s great for private areas of your web app where SEO doesn’t matter or React Native. That is to say, public portions of your web applications are very important. It’s where you get your users/customers from. If you can distill all data-fetching on the server to one database request you’re literally making money. And if Redux can finally have a routing framework that puts things in the right place, even better.

componentDidMount WITHOUT APOLLO

As for componentDidMount without Apollo, there are various reasons people have gravitated toward data fetching in componentDidMount.

The primary one is likely that they don’t have Redux under control. And if they are using Redux they don’t have their async middleware under wraps. They might not even have it at all. It’s this category of users that are hell bent on fetching data in stateful components.

It may also be a matter of habit — a habit that started before they introduced Redux to their app.

That’s not to say that the componentDidMount “workflow” isn’t at times a useful one, even for the most advanced Redux developers. Sometimes you just want to get your component working and want to fetch the data right where you are without visiting other “boilerplate.” Or you want to use the component in multiple places and be sure it will have all its data-dependencies. If SSR isn’t on your radar, it’s easy to see why componentDidMount is a viable option.

For me personally, SSR is always on my radar, and I’ve had RFR available to me for almost a year, so fetching data in componentDidMount is never something I consider.

The workflow thing is important. Let’s not discount it. RFR also has a nice workflow. Once u have URL-ized your actions, it’s extremely intuitive to pair a data dependency to an action in the form of a thunk. It’s a nice convergence of typical Redux patterns and terminology with routing.

✓ DERIVE STATE FROM THE URL— WHAT ABOUT MIDDLEWARE?

We’re going to assume you [now] understand/agree_with the importance of the URL, and you’re very hopeful that you can refine critical URL state into application Redux state.

So assuming you understand that and your plan is to pre-populate your Redux store and then render, what options are available to you to best populate your store:

  • Parse the URL, manually fetch data from an API server, and then dispatch an action with the returned data
  • Dispatch an action function to your redux-thunk middleware that grabs some state you already have, combines it with URL state, and then fetches data, and then dispatches the results
  • Sagas that take an action based on the URL which then basically do the same as redux-thunk but with a synchronous feel
  • Observables which once again do the same thing, but which excel in dealing with lots of fast events (streams)

It’s basically all the same stuff. And you have options. Options of what asynchronous middleware to use. Options of when to parse the URL and what to initially dispatch. We won’t have the “what’s the best asynchronous middleware” debate today — because the answer is you can still use them with Redux-First Router. The recommendation is you use them where they shine though.

But if you want my opinion, async/await solves 80% of what Sagas is targeted at, and you should save Observables for when you have true steams of data/inputs, not occasional taps/clicks, nor fetching-related debouncing.

Even though Redux-First Router includes a middleware, it’s important to think of it as something simpler than that.

What all the above solutions have in common is that you dispatch a synchronous setup action and a follow-up action is dispatched a few ticks/milliseconds/seconds later with the data. Asynchronously.

In other words, the vast majority of the time you simply want to set an initial state, and then set a second state depending on how the data fetching job went.

Sagas and Observables gives you tremendous power to go far beyond this, allowing you to listen to complex/multiple inputs and dispatch however many follow-up async actions you need. But that doesn’t change the fact that most of the time it’s just one upfront and one after. That’s the 80% use-case. The 80% use-case for Redux middleware is dispatching 2 actions in sequence: the synchronous setup action and an asynchronous follow-up action.

If you’re dispatching more than 2 actions for such things, you might very well be doing it wrong. I’ve seen lots of apps start leaning towards needing action batching middleware, etc, when all they really need is to do is expand the variety of actions reducers respond to.

So since RFR works beautifully with Sagas etc (we recently added one thing that made the Sagas experience even better by the way), wouldn’t it be nice to have a stupid simple idiomatic solution for these ever-common back-to-back dispatches?

Yes, and the answer is to pair follow-up actions to their trigger/setup action in an idiomatic way. Perhaps a config object, namely a route.

2 routes in Redux-First Router — one dispatch in your thunk (aka “follow-up action”)

Each route in RFR is basically an action. It’s a way to apply more context to your actions. The core of that context is that each action corresponds to a precise URL. But there are also more things you can do — from prefetching chunks (advanced), prefetching data (advanced), to what we will be talking about for the remainder of this article and the most important thing (as well as the basis for prefetching): fetching data via your “route thunk” option.

Each route config object has a thunk option, which looks just like the thunk functions you dispatch to redux-thunk — with one exception:

  • within the thunk, you don’t need to dispatch the initial “setup action.” You know, the one where you set some state to show a spinner.

The reason you don’t need to waste your energy dispatching a setup action within your thunk is because the route is the setup action. When that URL is “visited” the corresponding action with path params extracted into the payload will be dispatched.

NOTE: if this is your first time looking at Redux-First Router, the type dispatched when you visit, for example, /list/javascript is LIST. The complete action looks like this:

{ type: ‘LIST’, payload: { category: ‘javascript’ } }

And linking/navigation operates the same way it does in React Router via <Link /> and <NavLink /> components. On the server you simply pass RFR the request path to get things going. We will be covering server rendering in depth in a future article.

So to reiterate, thanks to the URL-ized context you can skip manually dispatching the setup action. It’s a small win, but as the benefits of URL-centric config unfolds, wins will mount up.

GREAT JUSTICE (DECLARATIVE > IMPERATIVE)

When you can distill solutions down to config objects, rather than ad-hoc code, you achieve great justice.

Truth is I came into this game — like many — in an era of MVC frameworks where all the rage was “convention over configuration.” And I still don’t know what that means — it seems both overlap quite a bit. But I’ll tell you: if I could configure my entire app as one big JSON blob I’d be one happy programmer.

You remember XML right? A tool used for configuration. Well HTML is XML, or sub or superset of it or whatever. And React specifically chose the JSX route. Why? Because “declarative” coding is half way from “imperative” coding to configuration. JSX is essentially configuration with a backdoor to write ad-hoc imperative code when you need to.

It depends on your definition of “imperative,” but I just mean you can write plain procedural javascript in your otherwise pure declarative component functions.

So, it’s for this reason that Sagas and Observables and ad-hoc thunks are closer on the declarative-to-imperative spectrum to the imperative side. Being able to define your thunks in a route configuration gives more meaning to those thunks, locks them in, and is therefore closer to the configuration/declarative side.

So that’s the premise of route thunks — meaningful URL context. You know exactly where your data dependencies are supposed to go. And that place is closest to URL state — the only state (minus your user session cookie) that you can synchronously render from.

Fancy middleware and RFR share another key concept/benefit: specifying data dependencies away from the place you dispatch requests, and in a centralized [hopefully] single place.

ASIDE: COOKIE SESSION STATE

As for cookie session state — it falls into the same category. That category is state you know upfront before you render your component tree.

The strategy for that is to call createStore server side with initial state (the second argument) containing your user object which you retrieved using a session cookie or JSON Web Token (also in the form of a cookie).

This article won’t tackle auth and the user session cookie, but the point is it’s in the same boat as URL state — so we can check it off in our minds. And similarly to how we call all client-side GraphQL caches simply “Apollo,” we’ll continue to call state from the URL even though it includes the user session cookie: “URL state” or “critical URL state.”™

REDUX’S CONTRACT REVELATION

To make sure we’re on the same page, let’s talk about “contracts” and what Redux’s core “contract revelation” was all about. The contract is:

  • your “UI database” is derived from pure functions called reducers
  • your reducers respond to serializable actions; that is, you can’t set state directly; you have to do so indirectly through the combination of actions + reducers

That’s the contract. The gains this gives you is 100% predictable state, and subsequently predictable view rendering.

These gains extend so far that you can provide the same sequence of actions and be guaranteed the same resulting state.

With this foundation, the opportunity of time-traveling debugging unveils itself as well as everything you see in the Redux Devtools. I live in there. If you build Redux apps, you probably use that far far more than the React Devtools.

I only use the later when I’m working on other people’s code. I use it as a cheat-tool to figure out what they did. For my own code, it doesn’t do much for me, since usually components rendered in the current component tree and their props are in my head, and their state is 80-99% of the time in Redux.

I remember the days when I used to debug in Chrome all the time. The Redux Devtools has basically displaced that too if you’re doing a good job at building your app out of pure functions, pure components, pure reducers, pure everything.

If you are doing those things, the Redux Devtools can answer your most common questions:

  • “well, did I dispatch the correct action?”
  • “are reducers responding to actions correctly?”

Subsequently since debugging these 2 are easy, and since pure components are also so damn easy to deal with, your primary bottleneck becomes the asynchronicity of data fetching + multiple dispatches.

Where RFR really shines is that in the midst of the asynchronous data fetching challenge, you don’t also have to deal with the URL. Just make sure the actions dispatched have a route matching it — once in the beginning when you initially setup your app (or every so often when you add a new feature).

It makes the URL an after-thought. And not an after-thought that causes you problems because you now have to re-architect your app. And certainly not something that requires additional dispatches.

See, that’s part of the problem here — developers end up dispatching too many actions. For example, in my apps, I do everything I can to avoid actions that synchronously dispatch one after another. I consider that “incorrect”.

Any time I’m tempted to dispatch consecutive synchronous actions, I instead figure out how to make my reducers respond correctly as if only one of the actions was dispatched.

Your reducers should be able to fully respond to the first action. There are reasons for batching middleware, but most of the time it’s an anti-pattern.

So if you have things setup where you have to dispatch one action to display one thing, another action to display something else, and yet another subsequent action (or function call) to change the URL, you’re definitely doing it wrong. Or it’s because you don’t have something like Redux-First Router at your service, and instead you’re littering lifecycle methods throughout your component tree with ad-hoc component-level dispatches.

In other words, component-level dispatches is the #1 cause of multiple synchronous dispatches, and ultimately crummy rendering performance + a disorganized redux architecture.

To be clear, there’s always an exception to the rule. Today we’re talking about the 80%, not the 20% of the 80/20 rule.

URL-MANAGEMENT OUT OF THE WAY

So with a 2nd problem out of your way (i.e. URL-management + keeping everything in sync), you have relieved a lot of pressure and complexity from your async data fetching.

Data-fetching now boils down to:

  • the user reaching a URL (no work for you)
  • you requesting some data (less work: no setup action)
  • making a decision on what to do with it & dispatching a single follow-up action (now better in focus as your sole task)

That follow-up action may be one of many possible actions. For example, if the user visits mysweetapp.com/entity/:slug, you dispatch a follow-up action that retrieves various related entities (based on the slug) you’d like to display.

Or if the user visits mysweetapp.com/me without being logged-in, you redirect back to the /login form. And if the user is logged-in, you asynchronously fetch some data you will need to display within their dashboard:

QUICK, ANSWER THIS: how do we trigger the loading spinner until ENTITIES_FETCHED is dispatched?

You no longer should worry about dispatching an initial setup action, as by virtue of visiting /me and the ME type being dispatched, your reducers should know to display spinners if the data isn’t cached.

As you can see, RFR offers a redirect action creator. It’s one of the few such utilities offered by the library, as the goal is to keep the API surface to a minimum. Most of your work is in the one-time setup of your routesMap and continued work via typical Redux patterns (e.g. within your reducers, connecting components, etc).

…While we’re here, another thing worth looking at is how routes NOT_FOUND can be addressed:

Dispatching the NOT_FOUND type exported from RFR is a solution for when you throw your hands up and don’t know exactly what to render because you didn’t get the data you needed — in this case, the videos for the given category.

You can simply dispatch NOT_FOUND, and depending on whether RFR can infer the URL or not, it will display the correct URL (even though it doesn’t have the data for it), or your notFoundPath as supplied as an option to connectRoutes.

From there it’s up to your reducers to determine what to display (when it receives the NOT_FOUND type) as they usually would. Also note: internally the system will dispatch NOT_FOUND when no route is matched.

Why do we bring up dispatching NOT_FOUND? Because like redirects, it’s part of the idiomatic ways to use your route thunks.

TAKEAWAY

The takeaway is you no longer have files full of ad-hoc action creator thunks that do who knows what. If that’s the path you took, you’re left having to read the comments to remember how everything works. See, the thing is URLs are something to be taken seriously. They force you to encode your app in as logical of a way as possible. They are much needed structure — contracts — that keep the actions your app deals with to a minimum.

When you see a thunk corresponds to a URL (say your /login path), and because of that you know it’s the only thunk that can handle that URL, you know exactly what to expect of that thunk. And you know no other thunks can deal with it.

You will inevitably struggle to manage your exponentially growing number of action creators if you treat them as setters. Having URL-ized actions prevents you from creating more actions than you need. As a result your reducers become fatter and smarter.

Whereas before they might have looked like this:

Now they look like this:

So in the former you often end up dispatching SIDEBAR_OPEN right after another action that does something else. Or at least you can easily fall into this trap.

Conversely with Redux-First Router your reducers become page-centric, and are now responsible for “tear down” in response to a bunch of actions it might not have responded to in the past.

HOW DID WE GET WHERE WE ARE TODAY

The fact of the matter is Redux took us in a direction where anything and everything became possible. And since users are likely to trigger all sorts of events, it became a nice way to be able to deal with anything that comes our way.

So with that flexibility, and React Router tricking us into thinking we were doing it the right way (by dealing with URL state within the View Layer), we forgot the old wisdom of MVC.

In MVC terms, React is the V, Redux the M, and RFR the C.

If you’re like me, you thought the MVC pattern was dead.

Bottom line: because new powerful tools have made all the possibilities possible, RFR gives us much structure in a world where we desperately need it. We have become children in desperate need of structure.

As in the initial RFR pre-release article, it’s been a case of getting lost along our way as new things opened up the pandora’s box of options.

In the RFR Pre-Release article, I describe how the “everything is a component” strategy is essentially a workaround for not having a client-side database like Redux synced with URL state.

In this article, we’ve debunked how fancy middleware is a workaround for not having a proper routing mechanism and its corresponding contextual structure (i.e. its routesMap).

To be clear, RFR is a middleware. It’s a middleware, enhancer, reducer, set of action creators and Link components (but no Route components). The middleware is probably the biggest part. So it is a middleware just like the others, but rather than give you infinite flexibility, it constrains you via contracts just like Dan Abramov promoted was what Redux owed its true power/utility to.

RFR isn’t the first router or framework to provide you a defined place to request data in response to path + query params. To seasoned developers, this isn’t anything new. Everything old is new again. What RFR is, is the first solution to take MVC to heart and apply it to a Redux-specific context — after a period where MVC has been anathema to everyone.

At the end of the day, you’re using routing to do the same thing you do the bulk of the time in more complicated super flexible middleware. Even if a few people manage to come up with some discrepancies and edge cases, you’re covering somewhere between 75 and 95% of all cases by having thunks attached to URL-ized actions.

FINAL COMPARISON

To wrap things up let’s do a final comparison between the old ways and the new Redux-First Router way.

If you made it this far, you deserve a break; play this song while you review the code comparison that follows (i.e. the real meat of this article)

Old componentDidMount + react-router + redux-thunk way vs. RFR way:

OLD componentDidMount WAY

Take note of the way react-router state contained within the view layer (ownProps.match.params.category) is combined with Redux state to derive the final state, as well as to dispatch current and future actions via requestVideos within componentDidMount , componentWillReceiveProps.

NEW REDUX-FIRST ROUTER WAY

The primary thing to notice in the new way — besides that it’s half the size — is that the requestVideos thunk has moved to a permanent place as the value of a route’s thunk. In both cases they are standard Redux thunks that receive dispatch and getState arguments. However, when using Redux-First Router, you no longer need to dispatch the initial REQUEST_VIDEOS action, as the LIST action is intrinsically used instead.

Also keep in mind, since the thunk is tied to the route, you can easily add or switch in other components that need the videosByCategory state or remove the original one, without worrying about whether the necessary data will be available. While it’s nice to co-locate data to components, there are just as many reasons why de-coupled data is an improvement to your workflow.

The next thing to notice is how category is retrieved from Redux state, rather than ownProps.match.params.category which only exists in the View Layer.

The last and perhaps most important thing to observe is how the new way uses a stateless component, while the old way requires both componentDidMount and componentWillReceiveProps to truly get the job done:

after looking at RFR’s route-centric approach, it barely makes sense to update your store from components anymore (unless it’s onClick/etc)

Redux-First Router automates this and re-calls your thunk as the user navigates from category to category, i.e. from URL to URL. When it comes to server-rendering, RFR really shines — you no longer need to discover which components have data dependencies; the router will resolve all thunks and prepopulate your store for you, before your one and only render. We’ll tackle SSR with RFR in depth in a future article.

As for the videos themselves, they both share the same primary reducer for storing videos by category:

However their loading reducers are slightly different

old loading reducer

Notice the RFR loading reducer responds to the innate LIST type you never had to dispatch, and how match.params.category has moved out of the View Layer to its own simple reducer:

new loading reducer + category reducer

Aside from contextual application of thunks, the most important improvement is actually the simplicity of receiving types and payloads corresponding to routes, as you can see in the screenshot directly above.

The LIST type, remember, is the key/name of one your routes. It’s basically a reference to a URL that was visited. This isn’t just some random type whose awkward name you settled on. This is a page.

To give this some perspective, here’s a quote from the react-router-redux 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.”

So in short, using React Router, you’re stuck with state in the view layer (and you already know what we think about that). Whereas with Redux-First Router, you can deal with URL state like any other state, and in a natural way. Might I say the way it’s supposed to be.

CONCLUSION

Redux-First Router gives your thunks context based on the URL, and by doing so reduces the number of action creators you need while self-documenting their purpose.

RFR brings into focus the core data-fetching work of your “follow-up” action by removing the need to perform URL/history-related chores and by performing the setup dispatch for you.

RFR forces you to write better, fatter, smarter reducers.

RFR eliminates the “state in the view layer” trap, as well as the aforementioned problems React Router has with syncing to Redux.

RFR’s configuration contract puts the breaks on exploding usage of middleware + thunks by providing you with a familiar yet Redux-specific interface that lends itself to “thin controllers.”

RFR nevertheless works with existing middleware and Apollo.

RFR makes server-rendering a first-class priority that won’t bite you in the butt when it comes time to address it. Code-splitting is equally straightforward.

Redux-First Router uses Redux terminology and idioms and is an excellent impedance match to Redux.

If you haven’t tinkered with it yet, you can do so right here. Here’s the routesMap.js file that has been the focus of today’s analysis:

UPDATE (July 23rd): Reselect has been implemented for the isLoading indicator and the videosByCategory reducer simplified by breaking it into an additional reducer: category. So this article won’t reflect exactly what’s in the CodeSandbox, but it will be a good learning transition, as I didn’t initially want to introduce Reselect.

--

--