Why normalize the redux state shape

I think this is best explained with an example.

Let’s say you want to display users of the website in a table. Your first thought might be to store the users as an array of objects, which might look something like this:

state = { users : [ { userId : 1 , name : "Callum" , email : "someEmail@gmail.com" } , ... ] , isFetching : false , lastUpdated : 1439478405434 , }

This is also most likely what the data you are fetching from your API looks like.

Whilst this would work in most cases, let’s imagine you want to find a specific user.

You would loop through the array of users until you find the correct ID. This wouldn’t be a problem with a small list of users, but as your web application scales, you could run into performance issues.

Futhermore, what about if you have nested data?

For example each user could have a list of blogs that they follow, with each blog object containing an id and a name.

This would result in the previous example looking like:

state = { users : [ { userId : 1 , name : "Callum" , email : "someEmail@gmail.com" blogs : [ { id : 12 , name : "Penguin Blog" , } , { id : 15 , name : "Cat Blog" , } , ... ] } , ... ] , isFetching : false , lastUpdated : 1439478405434 , }

As you can see, things are starting to get complicated. What if two users follow the same blog?

Data could easily get repeated here.

As the redux docs state, this is a concern for multiple reasons:

We might have to update the same piece of data in several locations.

Trying to update a deeply nested data structure can be a horrible experience. Imagine first looping through to find the correct user, then looping through to find the right blog etc..

The redux store is immutable, meaning that every time we make a change to this nested data structure, all ancestors in the state would also have to be updated. This would make UI components re-render unnecessarily.



The solution

More often than not, we structure the state shape of our redux application in the same way that we receive the data from the API. However, it doesn’t have to be this way.

Redux suggests we learn from our past mistakes (Databases), and store the data in a normalized form.

Normalization refers to transforming the schema of a database to remove duplicate data. The biggest advantage of this is having a single point of truth, meaning there is only one place in the database that contains the true value of some piece of information.

Normalizing the redux state shape

Instead of storing data as an array of objects, we store the data as an object, indexed by a unique id.

But Callum.. if we just store users as an Object, we would have to call Object.values() to turn it into an array when rendering rows, which would be cumbersome.

I agree it would be cumbersome, but there’s a solution to that as well:

In addition to storing each user as an object, we store an array of unique user ids, to indicate ordering.

Here’s the normalized version of the previous example:

state = { users : { byId : { 1 : { userId : 1 , name : "Callum" , email : "someEmail@gmail.com" blogs : [ 12 , 15 ] } , ... } , allIds : [ 1 ] , } , blogs : { byId : { 12 : { id : 12 , name : "Penguin Blog" , } , 15 : { id : 15 , name : "Cat Blog" , } } , allIds : [ 12 , 15 ] } isFetching : false , lastUpdated : 1439478405434 , }

With this normalized state shape, you can find users by id in O(1), as well as still getting all the advantages of an array based state.

Furthermore, blogs and users are separated, with no deep nesting. They are linked via an array of blog ids per user, as highlighted (a foreign key if you will).

Instead of updating a blog in multiple places now, we can simply update it within the blogs section of the state. This means that the users slice of state doesn’t mutate. Therefore only the UI directly related to the blogs state will update.

We also don’t need to dig through the user state to find blogs, we can directly go to the blogs state now.

To render the rows, you don’t need to do Object.values() or anything like that.

simply loop through the id array, then find the user by id like so:

for ( const userId of users . allIds ) { const user = users . byId [ userId ] ; }

Normalization isn’t perfect

Like everything, it does have it’s trade offs.

Firstly, it can take a lot of time to set up. But I personally think there’s two valid responses to this problem. One is normalizr, which lets you automate this process. Secondly, I would like to say that although it requires a bigger investment in time initially, it does result in and easier development process in the long term, especially when requirements change and state needs to be modified.

Another issue I’ve read about is that it can create a hard to debug layer between your API and code, especially concerning data-related bugs. I have to admit, this hasn’t personally happened to me, but I can see this happening.

Also at some point, we’re going to have to de-normalize the data to show it, so how do we go about doing that correctly?

The answer is selectors, which I will be explaining and showing in my next tl;dr post.

Conclusion

Well, that just about wraps up this short tl;dr on normalizing redux state.

Here’s some links for extended reading:

Let me know what you think in the comments, and as always, sign up to the emailing list if you want to see more posts!