One of the nice things about React and Redux is that when the state changes, the DOM automatically rerenders. In other words, if something changes behind the scenes, either because of user actions or new data from the database, then what is displayed on the screen will be updated automatically. This takes a lot of work away from the programmer and lets computers manage the apps instead. However, what do you do when the state changes and the DOM doesn’t update as you expect?
While working on a project, my team came across such an issue. The project was to create an app that facilitates cycling races by keeping track of which riders participated in which races. Our Redux state would change (we could see it updating via Chrome DevTools), but those changes weren’t showing up in the user interface (UI). After eliminating the possibility of syntax errors, we were left wondering what went wrong. How does Redux compare the previous state to the current state? How does it determine when to update the DOM?
After looking in the usual places on the internet (namely, Stack Overflow), I decided to just look at the Redux source code itself.
(From https://bit.ly/32Mig7I from react-redux source code on GitHub)
Basically, Redux first compares if the states are referencing the same object, then if they have the same number of keys. If it has the same number of keys, it checks if the keys are the same and if the same keys have the same values. If those are all true, then it assumes that the state hasn’t changed. This was the cause of our issue: inside the state, we had a list of objects and each object had a key with a value that was a list of objects, and we were making changes to a property of one of those objects, such as the rider ranking in the example below. Too deep for Redux to catch!
(An example of how our state was structured)
Now that I knew the cause of the issue, I started researching what other people did to avoid this deep comparison. The most notable thing to do is to organize the state into a normalized state. A normalized state acts a lot like a relational database in that it uses IDs to relate objects. For example, with our cycling project instead of having a race object with a list of rider objects as a property, you have a race object with a list of rider IDs and the rider objects housed under a different property in the state. This creates a much more efficient state for retrieving and updating, and avoids repeating information. And it actively avoids the deep comparison problem because the state won’t be too deep for the shallow comparison to catch.
Unfortunately, using a normalized state works a lot better if you have a normalized state from the beginning. We had to balance rewriting all of our code and fixing the state’s structure. To fix our problem, we ended up reorganizing the state to make it more like a normalized state, while minimizing how much code we had to rewrite. So if you find yourself with a state change that isn’t reflected in the UI, consider normalizing your state from the beginning to avoid headaches later on.