Observations on Redux, Routing and Application Architecture

Michael Tiller
8 min readJan 13, 2016

--

React has been on my radar for a while. My main product was developed
in Angular and I was reasonably happy with it. But as with all technologies, you eventually find the limitations of each approach and cast a wandering eye toward other technologies that might help you overcome some of those limitations.

In order to understand what really pushed me to learn more about React, I should explain that I’m a big fan of TypeScript. I suppose this makes me a minority in the Javascript world. I’ve written lots of code in dynamically typed languages and while I agree there is a certain expediency to getting started, I always find that refactoring is a lot easier in statically typed languages. So I migrated my Angular 1.x code to TypeScript and I’ve been very pleased with my ability to safely and quickly refactor things.

So what does that have to do with React? (much less Redux, routing, etc) Well, one of my frustrations with Angular was the templating system. Specifically, the fact that while my code (via TypeScript) was now being statically checked, my templates were not. What I wanted was something like the Play! Framework’s statically typed templating system. As of version
1.6
, TypeScript began supporting a typed version of JSX called TSX. Not
only did this give me static checking of my HTML, it did it as part of the principle compilation step.

Thus began my exploration of React. I started small by using ‘ngReact’ to add React components to my Angular 1.x application. In this way, I gradually became familiar with the component lifecycle and flux architecture approach. This really resonated with me and I gradually started expanding to incorporate some of these ideas into my existing Angular app.

I am a big Scala fan and, as a result, I had looked once or twice at Elm. At the time, it really didn’t feel “production ready”, but I loved the ideas in Elm. The use of a state monad, lens, static typing, etc. brought many of the aspects I really liked from Scala into a web development context. So I was already primed for the state monad approach when I came across what Dan Abramov was doing with Redux. I must admit that I didn’t quite “get” Redux when I first saw it. Part of my initial frustration was related to static typing implications. But I eventually came back to Redux and gained a much greater appreciation for the simplicity and scalability of Redux.

While I’ve been working on some real projects using React and Redux, I still consider myself something of a newbie. So take everything I say with a grain of salt. On top of that, many of my opinions are shaped by the implications that static typing has on my workflow and these are considerations that most people probably don’t really care about. Nevertheless, I think the overall gist of my assessment could be interesting to the wider React/Redux community.

Testing

Much of what I have to say centers around testing. If you take away one thing from what I have to say, it should be that applications should be architected so that you can do as much testing as possible without a view present. That may seem obvious, but I see lots of code where application logic is included as part of the view. The standard I try to follow is that I should be able to use `mocha` to test all the logic in my application. This constraint is what has driven my thinking in these areas.

Keep in mind that what I’m talking about is not using something like server-side rendering in conjunction with React. I want a testing framework that is focused on actions and states and leaves the view out of things. So while you can write tests that do things like shallow renders in the absence of a browser, you are still testing the artifacts of the application state, not the application state itself.

I’ve gone so far as to create an implementation of TodoMVC that tries to follow this philosophy as much as possible. This has impacted several
aspects of application architecture…

Routing

The first thing I ran into was related to routing. I’ve already written in greater detail on this. I would summarize simply by saying that I have found it very useful to think about routing as an abstraction above the view. I’ve included several routing related capabilities in my vada library related to defining, transitioning between and reacting to changes in routes. These involve representing the current route in the application state, defining routes as first-class objects (vs. string), injecting logic when the application enters or exits routes, mapping URL patterns to specific routes and a React based router to declaratively specify which components to show given the current route.

Routing is really a subset of the total application state. But routing is, in some sense, the boundary between application state and UI state. I think of routing as “what do I want users to be able to deep link to?”. Do I need a route that goes so far as to specify what field, in a form, currently has focus? Almost certainly not. Focus is UI state. Do I want to link to a particular customer? Almost certainly. So the extent to which routing is represented in application state depends on what I want to “deep link” to. As a consequence, some parts of my state might be specific to the route I am currently in which leads me to…

Application Logic

Reactors are an abstraction I use to help me respond to changes in application state. This allows me to integrate logic into my applications based on state, not via rendering. This is important because I think dispatching actions during rendering is an anti-pattern (note: I’m not talking about dispatching actions in response to user input…that is entirely different).

So if we don’t dispatch actions during view rendering (which, in my mind, includes any of the component lifecycle methods), then how do we integrate it? That is where Reactors come in. Reactors are ways of “enhancing” existing reducers with functionality that responds in a predictable way to changes in application state.

The best example of this would be code to handle entering and exiting specific routes. When I enter a route associated with “customer 25”, I’ll need to load my application state up with information about that customer. If this is synchronous, the Reactor will load the data as part of the normal part of the reducer evaluation which means that data will be loaded before the view is rendered. It becomes internal to the reducer (without side effects, details). If a particular route depends on some asynchronous process to adjust application state, then the Reactor can simply be used to trigger the asynchronous request. But even in the latter case, Reactors provide a predictable way of responding to changes that isn’t really provided by adding a listener via ‘subscribe’ on a store.

Operations

One thing I find a little odd about how people typically implement their Redux reducers is how they separate the action identity from the action effect. I prefer a closer association between these and that is why I’ve created a library of functions for constructing reducers in terms of actions in such a way that the action identity and its effect are more tightly coupled. The code looks something like this:

const entryText = DefOp(“entry”, (s: AppState, p: string) => {
let ret = clone(s);
ret.entry = p;
return ret;
});
const deleteItem = DefOp(“delete”, (s: AppState, p: number) => {

});
const reducer = OpReducer(initialState, [entryText, deleteItem, …]);

Note that this is in TypeScript but you can easily just ignore the type annotations. The idea here is that each action is a first class object that not only represents the objects identity but also the effect that action should have on the application state (i.e., it includes its own “reducer” function that takes current state, action data and returns a new state).

This has several advantages in my opinion. First, it allows me to keep action identity and effect close together in the source. It also makes it possible to generate the complete reducer with a single function call to ‘OpReducer’. Another advantage, behind the scenes, is that the actual identity of each action is bundled (behind the scenes) with a UUID4 string to avoid the possibility that two actions (associated with unrelated reducers) inadvertently have the same ‘type’ field and accidentally trigger code in the wrong reducer.

Dispatching the actions is quite simple, e.g.,

store.dispatch(entryText.request(“Take out the garbage”));

Here the `request` method is an action creator specifically for this type of action. An additional benefit, in a TypeScript context, is static type checking of action arguments.

Modules

In considering application architecture, it is worth recognizing the role that modules play. It is easy to overlook, but in the ‘npm’ world, it is particularly easy to keep code organized and modular by leveraging ‘npm’. Tools like ‘webpack’ and ‘browserify’ have taken this functionality and enhanced it considerably by making it very easy to bundle this independent modules back into a single code base for deployment.

One thing I am struck by is how we are in the midst of a cycle of breaking everything up and re-evaluating it. My observation is that through time, we go through cycles. During some periods, when there is general consensus on useful patterns, we tend to get frameworks that bundle everything together (a so-called “batteries included” approach). Angular 1.x is, in my opinion, a good example of this. This isn’t bad when large numbers of people have a common understanding of the way things “should be”. But then we go through periods where we want to revisit things. In those cases, we prefer things to be more “a la carte”. I think React and Redux reflect a shift in that direction. Typically, the issue with “a la carte” is that it is more work to combine the pieces you want easily. This is another area where I think ‘npm’ has really helped the community out in ways we don’t necessarily notice. Instead of an MVC framework, we really do have the ability to easily pull together independent modules to handle the M, the V and the C (and other aspects as well) separately. I think this is helping to accelerate the pace of innovation by making it easy for people to iterate quickly and evaluate the utility of different combinations.

I don’t really have anything to add or any new functionality to contribute here. I just think it is worth observing the benefits that this kind of modular code management can have on overall application structure.

Conclusion

My goal with this essay is simply to touch on my thoughts with regards to Redux, React and how they’ve fundamentally changed my view of building applications. This applies not just to web applications, but to native and desktop applications as well. In order to avoid going too far into the weeds, I’ve linked to more detailed essays that go into some depth about the topics I’ve mentioned.

The bottom line is that there are really solid benefits to architecting your code so as to keep application logic separate from the view. I think this is well understood. But at the same time, it is easy to get into a rut where we lose the discipline to do so. I’ve outlined what I think are a couple of useful patterns to help maintain this separation. My hope is that by talking about these issues, it inspires people to be more disciplined with their approaches and hopefully this kind of thinking will have a positive impact on libraries and frameworks out there to take steps to help maintain this separation.

--

--