Roughly a year ago I embarked upon building my first large React/Redux application. Over the course of a few months and a couple dozen thousand lines of code I learned a bunch about how to organise large application with those libraries. However, upon the ‘completion’ (if large internal apps can ever be considered complete) of the initial version of the app, one thing stood out: there was a lot of fairly repetitive Redux code.

At the end of the first project, this was the architecture we had. We had tapped into the idea of having ‘simple’ actions for certain common actions, such as updates and resets, with orchestration primarily falling to container components. ‘Complex’ actions (e.g. xhr) took advantage of the thunk middleware. Selectors, however, were fairly basic in this iteration. For the most part they ferried whole chunks of state to container components without too much in the way of mapping.

We as a team (of two) certainly never subscribed to the ‘a reducer/action for every single property’ idea, and had cut down the bloat a substantial degree via helper functions. Still, there it was, thousands upon thousands of lines of reducers, actions, and selectors. Sure, it was relatively well organised, but it all cost time. It took time to test, it certainly took time to write, and maintaining a larger codebase is inherently more likely to be difficult than a smaller one.

There were also a couple of outstanding issues that still hadn’t been tackled effectively enough for my liking. Namely:

Providing some level of type checking when updating the store

Eliminating action constant conflicts

The motivation behind addressing the first issue is simple; I wanted to have certainty that a given property is a Boolean, or that a given reducer’s state is an array. Certainty is great when, even in the paradise of immutable data, some part of your application could easily dispatch an action which replaces your array with a null.

Which leads me nicely onto the second issue; I also wanted to ensure that every action type was unique. For a small application it’s absolutely fine to have action names such as update_status (or even just update if your application is really small), but that luxury evaporates rapidly as an application grows. Unique action names are a must, but relying on a developer to know every action type that already exists in a mammoth application is unrealistic. Extend that to every action type in every module to be added to the application that other teams are currently working on right now and you’re in for an amazing time if the issue isn’t addressed early. Conventions help (e.g. giving a module’s actions a namespace in the constant), but something automated would be great!

Determining the scope

To alleviate these pain points, I needed a tool which could drastically cut down on the time needed to build reducers, actions, and selectors. It needed to provide some form of type validation, and it also needed to ensure that all of the actions it created were unique in order to prevent clashes.

Whilst the goal was clear, there was still some fuzziness on how much responsibility this tool should have. Should it be extensible, allowing custom actions and any behaviour the end user may desire? Should it try to provide a complex assortment of selectors? Should it allow entirely custom reducers? The possibilities were endless. Unfortunately, endless possibilities at an API level aren’t ideal.

So I took Redux and thought about its place in a React/Redux application. It maintains state and ultimately not much else. Storing and updating data is nothing new; databases have been storing data for decades. In fact, Redux is extremely simple comparatively. This realisation inspired me to start framing redux as a (crude) ‘database’ within the application.

The state that the reducers maintain is vaguely similar to the idea of having a collection in a database such as MongoDB. There’s no real need for the reducer itself to do much beyond a small set of fundamental state-altering behaviours. Everything else can be built on top of these behaviours, which are triggered and supplied with relevant data by actions. Querying the reducer state falls to selectors.

Like in databases, there’s no need for the fundamental behaviours used in reducers to be complicated and all encompassing; I wouldn’t expect SQL insert statements to also calculate the 23rd prime number. Instead, they focus on the bare minimum required to effectively maintain the store: updates, deletions, and resetting to a reducer’s initial state.

This simplification was powerful. Redux now had a strictly defined domain, and a pretty simple one to boot. So, in my week break between leaving my previous job and joining Onfido I spent a couple of days taking this idea and turning it into a reality; creating the initial version of what I eventually called Redux-scc (store chunk creator).

Enter Redux-scc

Redux-scc’s job is simple: take a ‘schema’ (heavily cribbed from React’s PropTypes), and build a ‘store chunk’. The store chunk contains one or more reducers, along with the associated actions and selectors to drive those reducers and make them useful to the rest of the app. It creates a foundation which a developer can build on top of using their own selectors and ‘glue’ functionality (such as Redux-thunk/sagas).

High level overview of how Redux-scc interacts with the rest of the application. Compared to the previous architecture, there is now a very distinct difference between ‘fundamental’ selectors and actions versus their custom counterparts. It is highly encouraged for orchestration of fundamental actions to occur within a thunk or saga, rather than a container component (which are focused on lifecycle methods and the redux ‘connection’). Likewise, the fundamental selectors are not intended to be exposed directly to the React layer (although sometimes that is still the best choice); as there is an emphasis on performing data transformation and calculations in the selector layer.

The actions (which have unique types) and selectors are generated dynamically and returned in a structure which mimics the structure of the store chunk. Thus allowing for arbitrarily nested reducers that provide their associated selectors and actions in a sane way. The reducers themselves validate data passed to them via actions and ensure that the structure retains the shape it was initialised with.

Taking it for a spin

Armed with this new tool, I was able to field test it with my second large internal app when I started here at Onfido. My initial impressions over the past few months have been extremely positive! The ability to quickly build up the store of any given new feature is liberating, freeing up time to spend improving the quality of any given feature. For example, here is a (somewhat) side by side comparison of the code needed for a simple reducer:

I say ‘somewhat’, as the vanilla Redux reducer doesn’t do anything to maintain its store shape in this case, let alone maintain the types of properties. Even without those features (and the test coverage required), it’s significantly more code than the Redux-scc version. More code means more to maintain, and more code to maintain means there is much less freedom to experiment and adapt to new requirements.

Development speed aside the other benefit, as previously mentioned, is type checking the reducer at run time. With this functionality in place, I can truly rely on my store with very little effort up front to keep it sane. I tell it that I want a reducer with three properties:

and I am guaranteed to get those properties with those types. I am also given a suite of actions and selectors to manipulate and access those properties which are guaranteed to work. As the action types are unique they will avoid conflicts even when the application is extremely large. No more wondering if the update_loading_status action has been already used somewhere else!

So there we have it, my attempt at reducing the bloat required when creating large stores, hopefully giving you some food for thought about how to tackle your own Redux bloat issues. Or maybe you’ve solved them in a much better way. Either way, I’d love to discuss it with you in the comments!

If you’re interested in looking at Redux-scc, it’s over on github and available on npm.