A Single Truth: Avoiding a Duplicate State in NgRx-Based Applications

Or Hemi
Riskified Tech
4 min readFeb 24, 2021

--

There is no doubt NgRx is one of the most popular frameworks for managing application state. However, once you finish the basic tutorial and start developing more advanced applications, you may encounter problems that are not mentioned in the guides. One such common problem that you may miss is the duplicate state. In this article I want to offer an approach for dealing with this problem.

Imagine an online shop application that offers various items. Users can add specific items to their wishlist; at the top of the page, they can also see items that are currently on sale. The application’s state is managed using NgRx. The straightforward way to do so is to create two pieces of state for the two lists in our store.

In our application, item prices are being updated constantly by the server, requiring us to update both lists each time. Furthermore, every time a user performs an action that might affect some of the items, we need to update those items on both lists. If, in the future, we decide to add a third category of items to our page, we will need to update that new list in our store as well.

This headache is called a duplicate state — two (or more) pieces of state that may overlap, and hold the same data. The data, therefore, ends up duplicated, as it is kept in the store more than once.

We no longer have a single source of truth. What does a single source of truth mean? In our case, it means that when enquiring, for example, about the price of an item, we have one source and one source only for the answer. We certainly cannot receive two different answers.

What do we need?

To present the user with two different lists of items while maintaining a single source of truth. That is, we need to hold the data for each item in the store only once.

How can we do it?

We know we cannot store the data duplicated. So we will unify the two pieces of state by merging them into one, creating a collection, or a pool, of items. Now each item is kept in the store only once.

But how can we present to the user two different lists of items while in the store we hold one unified collection? The answer is to use the power of selectors.

Selectors are pure functions used for obtaining slices of store state. We can use a selector to extract the pool of items, and on top of that, two selectors to calculate the original lists — one for items on sale and another for items that are on a user’s wishlist. That’s it!

We still end up with two different lists like before, but without (potentially) holding items in the store twice.

The components are agnostic to the change since they are only calling selectors, and selectors encapsulate the store structure. There is, therefore, no need to change anything in the components’ logic.

What we achieved

Item maintenance is much simpler thanks to the fact that we now have a single source of truth. This also enables us to discard unnecessary logic regarding item updates.

Drawbacks

This is no magic solution, and there are some drawbacks when choosing this approach. One thing is that removing items becomes complicated — since other components may need the item, we can’t remove it from the pool. The solution is to filter out the item using the selectors instead.

Another issue you may notice is inefficiency — we are, after all, calculating each list on every update. However, keep in mind that selectors are memoized so that calculation is avoided when the pool is unchanged. Furthermore, in most cases, there will be no noticeable performance impact unless the pool is very heavy.

In return, we gain clean code and a pure and easy way to maintain our items — a solution that will continue working even if we add more lists of items to our application in the future.

In conclusion

As NgRx applications are becoming more complex, you may encounter the duplicate state problem. Remember to store your data in a more raw, generic form, and extract it for each usage using selectors. Ask yourself if the collection might get heavy. If that is not the case, consider choosing this approach of maintaining a pool of records.

--

--