Normalizing Redux stores for maximum code reuse.

One of the primary benefits of Redux is that your application state is contained in a single source of truth, with data stored in a normalized structure. This lends itself well to business logic being shared or extended with minimal effort. The downside is that Redux requires a non-trival amount of boilerplate — but this flexibility is, in my opinion, one of the major benefits that boilerplate is purchasing you.

Object oriented solutions like MobX reduce boilerplate, but leave you on your own to shape your application in a way that’s testable, cohesive, and not overly coupled — in other words object-oriented analysis and design. Neither option is “better;” this is a tradeoff every project should make, based on its requirements. I’ll add, for emphasis, that I use MobX professionally at work, and love it. It’s entirely possible the overhead of OOP is less than what Redux requires, or that legacy code written in some older OOP-based framework, like Knockout, can more easily be converted to MobX than normalized Redux stores.

This post shows one use case that highlights Redux’s strengths. I didn’t go looking for it; after getting back on one of my side projects, I was soon adding a feature that works especially well with Redux, and I was motivated to write about it, given some of the criticism I’ve been seeing about Redux lately.

This post assumes the reader is familiar with Redux, including normalizing your stores. A previous post of mine discussed this at length. Code samples below will make liberal use of reselect, which is a simple tool for memoizing Redux selectors.

Use case

As before, the code samples are from my book-tracking website. I use it to store my own library, and also to try out new stuff. I learned React by writing it, and as a result got to switch my team over to it at work with great success, so all told it’s been time well spent :)

Specifically, a user can store a collection of hierarchical subjects: for example, American Revolution may be under American History, which may be under History, and so on. The hierarchies are stored via materialized paths in Mongo.

I’ll briefly show how the subjects are stored in code, and how this normalized state lends itself well to being re-used in subtly different ways.

Let me stress the following: nothing I’m about to show could not also be accomplished with MobX without that much extra work; my point is merely to show where and how Redux performs not only well, but better in my opinion than the alternatives.

Storing the subjects

Here’s a simplified version of my main, application-level store.

When the subjects come back, they’re flattened into a hash. Then, methods for shaping this hash back into a useful collection are exported, and used as needed by different parts of the application.

For example, the part of the application that lists books, allowing you to search for and select a subject to filter by, or add to a book looks in part like this

That same subjectHash is plucked from a different part of the application state, and then shaped with the exported stackAndGetTopLevelSubjects method, the results of which combined with the rest of that reducer’s state.

Extending this code

More interestingly, there’s another part of the application dedicated to editing subjects. All hierarchical subjects are listed out, and the user can drag and drop them onto new parents. But, as a subject is hovering over a valid new parent, that parent will show the pending subject in its children, so the user can get a visual sanity check before completing the drop. (styles are a work in progress, as always)

The reducer in question accepts actions indicating the drop is pending, and from there it’s fairly simple to tweak the subjects hash to create the new, temporary subject, and have it show up under its pending new parent.

Basically the 5 lines under if(currentDropCandidateId){which creates a shallow copy of that same subjects hash, creates and adds a copy of the dragging subject, with the path to make it a child of the drop target, and that’s it.

And if there is no currentDropCandidateId, then the imported subjectsSelectoris called. Yes, this selector does just call the same stackAndGetTopLevelSubjectsinternally, and so I could just call that same method below the if and ditch the else to save 3 lines, but subjectsSelectoris separately memoized, so these calls never cause any re-computation.

Along those lines, it is obnoxious that every time there’s a drag, each subject has its children re-computed in stackAndGetTopLevelSubjects, even if they’ve obviously not changed, but this, too, can easily be improved with simple caching and an ES6 Map. It’s doubtful any of these perf optimizations make any difference in real life here; I only do them because I’m irrational and the inefficiencies annoy me, and because it’s good experience working with these tools.

The benefits of normalization

Could this have been done with MobX? Of course. And it wouldn’t really be that hard. Presumably there’d be a main, subjects observable array — from there I figure there’d be a computed that would read both the main subjects array, and the current drop id. If no dropId, the main subjects array would be returned, simply enough. And, if the dropId were set, then the target subject would have to be cloned and replaced, with a copy of the dragged subject added to the target’s children array — all without violating any MobX invariants: observables can never be modified in a computed property definition. And, of course the target subject would have to be found, no matter how deep in the hierarchy it is, so either some sort of search to find it, or a computed lookup map defined off of the main subjects array. It’s doable, but I honestly think the Redux solution would be the more straightforward.

Or maybe there’s a simpler way I’m not seeing. Even if so, that still demonstrates my point: Redux forces you into a paradigm that’s fundamentally flexible and simple.

Conclusion

There’s no silver bullet. Both approaches have pros and cons that should be understood. The Redux approach of storing normalized data, shaped as needed offers a unique flexibility, at the cost of lots of boilerplate, plus code that may be unintuitive to the uninitiated.

If that flexibility is not in high demand for your project, you may very well be better off with MobX. Also, the MobX ability to easily create cascading reactions lends itself well to some use cases, “spreadsheets” being a common example.

I suppose my main motivation for writing this is to try to combat the increasing criticisms I see Redux getting. Yes, it requires more code, but it also offers unique benefits.