Redux: Normalizing the State Shape

Share this video with your friends

Send Tweet

We will learn how to normalize the state shape to ensure data consistency that is important in real-world applications.

Jiaming
Jiaming
~ 8 years ago

Thank you Dan! This series of lecture is really helpful! Would you please tell me the advantage of persisting lists of data as Objects rather than as array themselves?

Ningze
Ningze
~ 8 years ago

He has already answered your question clearly at the beginning of this video.

mobility-team
mobility-team
~ 8 years ago

Don't forget to clear the localStorage, when reloading the app with the state change ;)

Manjit Kumar
Manjit Kumar
~ 7 years ago

I am bit confused about the use of array [action.id] as key in state object for a todo instead of plain string action.id, If there is something I may have missed please point out or help me understand why it's like that.

refer- https://github.com/gaearon/todos/blob/11-normalizing-the-state-shape/src/reducers/todos.js#L10

Manjit Kumar
Manjit Kumar
~ 7 years ago

Just read about it here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Computed_property_names.

yiling
yiling
~ 7 years ago

Since the state.byId already has all the todos, why do we need getAllTodos constant again?

const getAllTodos = (state) =>
  state.allIds.map(id => state.byId[id]);

=========

Looks like I cannot delete a post, I am going to answer my own question here. getAllTodos function transforms a todos object into an array, this array maintains the existing contract within getVisibleTodos.

Eleni Lixourioti
Eleni Lixourioti
~ 6 years ago

This approach of having two reducers, essentially creating two "sub stores", is a bit like de-normalising rather than normalising. I could see why someone would do that as an optimisation, but I can also see how the two sub-states can get out of sync.

For example if you implement DELETE_TODO you can easily forget to add an implementation in the second reducer.

Maybe a different data structure like a Map (easy to get all values, easy to get all keys, easy to check for membership) would suffice. That said, Maps are still second-class citizens, as you cannot have nice things like spreading and you end up converting them to arrays and objects far too often in practice, so maybe this is not ideal either.

J. Matthew
J. Matthew
~ 5 years ago

Thank you Dan! This series of lecture is really helpful! Would you please tell me the advantage of persisting lists of data as Objects rather than as array themselves?

@Ningze The answer at the beginning of the video could be clearer and would benefit greatly from an example. I think Dan is positing that a real-world app might duplicate the same data structure (e.g. a ToDo object) across multiple arrays. For instance, if we stored the generated completed and all arrays in state for some reason (rather than just sending that data to the UI), they could both contain a complete copy of the same ToDo object(s).

That would be a bad way of doing things, because then if we updated the state of a given ToDo, such as to change the value of its text property, we'd have to make that update in both places, or else the two would fall "out of sync," as the video says.

The better way is to store the full data structure in one place, then simply include a reference to it in the other places. Using an object isn't strictly necessary for this—you could just as well store the full ToDo object at a certain index in the all array, and then store only that index in the completed array. You could then do something like this (not that different from what Dan does):
const completedToDos = completedIndices.map(index => allToDoObjects[index]);

However, using an object often serves this purpose better, especially for databases—and @Jiaming this gets at your question—because objects enforce uniqueness better than arrays. It's possible that two ToDos could have the same text and the same completed values, but they should never have the same id. There's nothing built into arrays to stop you from adding two ToDos with the same id, because the array elements are indexed by position in the array, so id is irrelevant. You'd have to write custom code when adding the ToDo to make sure none of the existing array elements had that id. But if you use an object instead, and index each ToDo by its id, then true duplicates are impossible.

Indexing a ToDo by one of its properties—in this case id—also confers performance benefits, because if you know the property value, you can look up the full object instantly, without having to search through an array to find the match. (You do want to be careful in an actual database not to index by any property that could change its value, which is why a UUID or the like is generally preferable.)

When you combine the two techniques of 1) indexing full ToDos (or whatever) by ID and 2) collecting references to subsets of them (like completed and all) in other forms with 3) a third technique of building additional objects that index whatever other ToDo properties to their ID (e.g. look up ToDo ID by text value) you can create a powerful system in which you can find whatever data you need instantly, and you never have to worry about updating it in more than one place*, which is pretty sweet.

*Of course, as @Eleni suggested, you do have to update your various lookups, but there's always a tradeoff.

J. Matthew
J. Matthew
~ 5 years ago

Since the state.byId already has all the todos, why do we need getAllTodos constant again? [...] I am going to answer my own question here. getAllTodos function transforms a todos object into an array, this array maintains the existing contract within getVisibleTodos.

You are right; however, it's worth noting that with the modern Object.values() method (which may not have been available when this course was recorded), we really don't need getAllTodos:

const allTodos = Object.values(state.byId);

J. Matthew
J. Matthew
~ 5 years ago

What I don't get is why this process is called "normalizing" the state shape. I understand the value of the changes made, just not what exactly that term means and why it applies here.

Will Johnson
Will Johnson
~ 5 years ago

@J. Matthew Hi. Thats a good question! From my research normalizing is a term from dealing with data that make its more predictable to deal with and easier to access. There are more in depth explanations on the egghead community forum.

https://community.egghead.io/t/normalizing-state-shape-in-redux/624