Querying a Redux Store

Redux is an outstanding tool for managing state in JavaScript, particularly in conjunction with ReactJS. While it’s documented outstandingly, figuring out how to structure things in practice can be challenging.


The section of the Redux docs covering React integration contains this

Any component wrapped with connect() call will receive a dispatch function as a prop, and any state it needs from the global state. In most cases you will only pass the first argument to connect(), which is a function we call a selector. This function takes the global Redux store’s state, and returns the props you need for the component. In the simplest case, you can just return the state given to you (i.e. pass identity function), but you may also wish to transform it first.
To make performant memoized transformations with composable selectors, check out reselect. In this example, we won’t use it, but it works great for larger apps.

This is followed by a code sample with a React component connected with a selector doing a slight transform on the store. It’s easy to miss here a key point: your store should contain flat, simple data that’s shaped as needed for your components in a selector downstream. This was also mentioned at the very beginning of the docs:

Note on Relationships
In a more complex app, you’re going to want different entities to reference each other. We suggest that you keep your state as normalized as possible, without any nesting. Keep every entity in an object stored with an ID as a key, and use IDs to reference it from other entities, or lists. Think of the app’s state as a database. This approach is described in normalizr’s documentation in detail. For example, keeping todosById: { id -> todo } and todos: array<id> inside the state would be a better idea in a real app, but we’re keeping the example simple.

I’m sure I’m not the only one who didn’t focus on this on my read through the docs. Unfortunately missing, and failing to internalize this advice will likely add a great deal of complexity and difficulty to your application

Example 1: Hierarchical Subjects

Imagine you’re writing a book tracking application, where books can be associated with zero or more subjects, and the subjects lie in a hierarchy: American History beneath History, and so on.

A function which stacks a flat list of these subjects (with MongoDB materialized paths) might look like this

It might be tempting to incorporate such a function directly in your store like this (in part)

Now the subjects coming out of your store are stacked and ready for consumption in the UI. This may seem ostensibly good, but it creates problems. Consider what would happen if the user wants to edit a subject deep in the hierarchy. You now you have to flatten the hierarchy to both find it, and update it when the editing is finished.

Painful. Of course there are duplicate and cacheable calls to flattenSubjects in the EDIT_SUBJECT action handler, and there’s a need to compose editSubjectsPacket a bit better. But that’s the least of the problems here: the code is overly complex.

Normalizing our data

The analogy to a relational DBMS here

We suggest that you keep your state as normalized as possible, without any nesting.

is surprisingly helpful. Storing subjects as a hash, keyed by _id yields a much better result. Our stacking functionality in the store is now simply

And the code to update modified subjects is much simpler

But now our store’s subjects list is just a somewhat useless JavaScript object with keys relating _id -> subject. To restore things to how they were, we use a selector when connecting our store to React. It looks like this in part


Pushing the DBMS analogy further, we’ve basically created a view on our data. The downside is that every single time our store changes, the subject stacking will be re-worked, just as the underlying queries are re-executed whenever you query a view.

Just as a database view can be materialized by creating an index on it, we have a (somewhat) similar way to avoid recomputing function calls in JavaScript: memoization. Since our selector here is just a regular function, we can memoize it as we can any other function. Of course we don’t want to manually do this; fortunately there’s a library we can use to do it for us: reselect.

Now our stacked subjects will only re-compute when the subjects list itself is changed. And yes, the arrow function in stackedSubjectsSelector is completely unnecessary; I included it only for clarity.

Performance, part 2

Now our subjects are completely re-stacked whenever any individual subject is changed. Returning to the indexed view analogy, this would be like an entire clustered index being re-built from scratch whenever any item in the underlying tables is changed — which of course modern engines don’t. It’s doubtful this will matter, but if this code were somehow performance intense / crucial, there’s nothing stopping you from manually memoizing the function, and only recomputing the portions of the tree that actually changed. Naturally this wouldn’t be easy, and should only be done if actually needed.

Example 2: Books’ Subjects

I won’t bother writing out all of the related code, as this post is already too long, but the same idea applies to having each book maintain a list of its subjects. While it’s tempting to stack each book’s subjects after the list is loaded, this has a couple of drawbacks: there’s now a temporal dependency, whereby the list of subjects must be present before the books are loaded; and the books’ subjects need to be re-stacked whenever a set of book results come back, and also whenever a subject is edited or deleted, so the affected books can have their list of subjects updated.

Applying the same principles described above, we instead just maintain a simple list of books and subjects, with each book maintaining a list of subject _ids, with a selector — booksWithSubjectsSelector — written to glue it all together for the UI.

This post is basically the result of me trying to write something with Redux and React, with a tremendous and generous amount of help from Redux author Dan Abramov along the way — help for which I am truly grateful.

That said, I’m sure there’s plenty left to learn here, so if there are further areas for improvement, please don’t be shy; I’d love to know about it.