Design a minimal and consistent state — an example

June 09, 2021

minimal and consistent — example

Last week, I discussed why aiming for a minimal and consistent state for your module is beneficial and how to keep your state free from data redundancy and data inconsistency. That recent post is full of motives and solutions; however, it is also theoretical. So, today, I’m going to present all advantages aligned with a specific example to show you how to handle such situations in practice.

Let the story begin

Some concepts are easier to understand when supplemented with real-life examples. Therefore, I will not come up with an actual implementation, and I will refrain from going into too much detail. Instead, I’ll try to keep my example specific enough to properly depict the idea and abstract enough to serve as a general guide.

Let’s say we have a module with a public interface and a private state. This component is here to render some data kept in its state. The interface will provide the data, thanks to two actions:

  • showData which gets data as an argument and saves them in the state,
  • clearData which removes data from the state.

When the module has data in its state, it should render them; when the state is empty, it should render nothing.
Now, let’s look at the possible state. Imagine you have three values in it:

  • doDisplayData is a Boolean that defines whether to render data or nothing,
  • displayType is an Enum that describes the presentation form of the rendered data,
  • data is an object that describes the data that should be rendered.

Data redundancy

The first thing you should see is that the state has some duplication. That is because some data are defined strictly by other data. For example, think about doDisplayData Boolean and data object in your state. How do they relate to each other?

Well, finding relations is one of the most important things when fighting duplication. When you understand that doDisplayData is true only when data is set, you can skip doDisplayData value entirely. Then, whenever you need to test whether you should render data, you can validate the value of the data variable.
Seeing a strong relation between variables allows removing redundancies and keeping the minimal state. This data uniqueness is essential when you think about more enormous applications that have an extended state. In such situations, having elements in your state that should actually be derived from the other part of the state increases the possibility of faulty modification and the cost of overall module comprehension.

Data inconsistency

You have two values left in your state that seem related — displayType, and data. The truth is that when one is defined, the other one should be too, and when one is empty, the other one should also be empty. However, this case is more subtle, and you need to see how the additional context changes your behavior.
This relation may indicate data redundancy when data value implies displayType value; then, you can deterministically calculate the latter based on the former. For example, it will happen when you can write a function that always returns the same type of display for a given ’data’ object. When you are in a situation like this, you can simply get rid of displayType because it is a duplication.

On the other hand, the relation may be more complex. For instance, you may not be able to determine the type of display based on specific data; thus, your interface function showData is probably getting two arguments (data and type) instead of one. Furthermore, it is possible to have a new interface action called changeDisplayType, which changes not the data themselves but the type of display. In that situation, you see the relation, but it is hard to tell all the possible values of a valid state. For example, what would you do with data defined and displayType undefined? Indeed, that is not a valid state.

As declared in the previous post, you can handle potential data inconsistencies by introducing a static code analysis. Static type checking is one of them. Try to think of the most comprehensive types for your state that will organize all possible combinations. You should unit-test the state update functions to make sure the result is consistent.
Another critical feature to fight the inconsistency is to understand the business motivation for your component’s state. Let’s call that the logical-state in contrast to the actual technical-state described at the beginning. That business state is the set of possible conditions that make sense from the module perspective. The better you are able to implement, type, and validate your technical-state to represent the logical-state and ideally keep it one-to-one, the easier it is to reason about the component’s behavior.

Conclusion

Well, this is an elementary example. It is simple because it needs to show the mechanisms of achieving a minimal and consistent state in practice. It is enough to understand why you should act, how to handle such circumstances, and what is the potential result of your actions.

When experiencing similar situations in more significant applications with an extended state, remember to look for relations and to fully understand the logic behind them. Then, you will most certainly know what you’ve found and you’ll implement beneficial adjustments.

Have a minimal and consistent state!