An opinionated guide to Javascript state management — Part 2: Essential vs Derived

Introducing essential and derived state, and how to implement both using Redux

Mike Yoon
The Startup
5 min readJun 15, 2020

--

Photo by Markus Spiske on Unsplash

In the second article of this series (click here to read part 1), I’m going to introduce the two types of state that exist in all user interface (UI) applications. The first is “essential” state, original information that cannot be calculated (i.e. a person’s birthday). The second is “derived”, information that can be calculated (i.e. a person’s age). While simple and seemingly similar concepts, they should be handled quite differently in code.

I’ll guide you through a simple example outlining the differences, and illustrate why it is so important to implement derived state with a separate pattern. First, let’s jump into the first (and simpler) of the two types, essential state.

Essential state

The first type of state is what can be called “essential”. There are likely other names for it, but the definition is the same. This is state that is original and cannot be calculated from any other available information. It must be provided directly by some source, i.e. the user, or an API. A classic example of essential state would be someone’s first and last name. From a code perspective, essential state is generally stored in its original form without transformation somewhere in the application. In our case we’ll be using Flux reducers to do this.

Derived state

Derived state, as its name implies, is derived from either essential or other derived state. Since it can be calculated, it would ideally not be stored in any permanent form in code, but rather as some kind of function. The benefit of separating them from the reducers is that leaves reducers responsible for just updating references. All the complex logic can go where it is best suited, into a specialized functions that can aggregate multiple pieces of state. We’ll use the selector pattern in this article to demonstrate this.

Simple example walk-through

In this article as well as the rest of the series, I’ll be providing code examples in mostly Redux. This is because I believe the Redux/Flux is the most explicit (nothing is done for you automatically) and makes for great teaching examples. Most (or all?) state management frameworks end up solving similar problems, so if you understand Flux, you should be able to easily understand other frameworks as well.

Let’s start with defining some essential state managed by a reducer. I’ve added some comments for those who are unfamiliar with Redux.

The initialState is setup with the user already logged in for simplicity, but I’ve provided an action to show we could arrive at that state if starting from a logged-out position. Everything managed by myReducer is “essential” state, but let’s see what happens if you decide to add a property called fullName to the state via this reducer.

Notice in the UPDATE_FIRSTNAME and LOGIN actions, both have to do the same work to calculate the fullName property. This isn’t too much of a problem in this simple case, but even then we should refactor the fullName creation into a function. In addition, you have to reference the existing state in the UPDATE_FIRSTNAME case, which isn’t a blocker here, but if the state you need is managed by another reducer you’d be stuck. Over time, as more complex state is added, the derived state will become more complex as well, and maintaining the reducers will become a challenge. Let’s look at the alternative that uses Reselect instead.

Now in this case, we have the getFullName selector that will automatically recompute the fullName anytime either firstName or lastName is updated. Anytime an action that modifies either of the name properties, the selector will take care of the rest and we don’t have to worry about updating that manually, which will be much safer.

Reselect memoization

What really ties this pattern together is Reselect’s memoization (most frameworks should have some form of memoization for derived state). It’s very simple, only storing the previous values of the input functions and the result function. Each time an action is dispatched, like someAction in our example, the two input functions on getFullNameAndTime will be called. Also, this action doesn’t result in firstName or lastName changing so the input functions will return values equivalent to the last. Since input values are equivalent, the selector will skip calling the result function and just return a reference to the previous value.

This memoization is what allows React to know when to re-render a relevant component, so even if our selector returned a complex object like { fullName } instead, it’d still only re-render when necessary. This will be important if you end up creating a hierarchy of selectors, feeding complex objects from one into another. I’ll show this in more detail in a later article. For now, let’s just examine how the input functions work.

Keep input functions simple

Reselect (and NgRx as well) selectors need to execute their input functions every time they are called. In Redux and and similar frameworks, you can assume selectors are called every time an action is dispatched. This means they must be as simple as possible. There are likely very few cases (or maybe none at all) where it is necessary to do anything other than simply return a value out of the state. Additionally, selectors only do simple equality === checks, so do not make an input function that returns a new object (state => { ... }) or something similar, as that will always fail an equality check and force a recompute.

You will need to be flexible at times

In an ideal system, all essential state would be handled by simple reducers (or equivalent in non-flux frameworks) as shown above, while derived state is all managed in selectors. Unfortunately, like most software engineering patterns, there are always exceptions. Oftentimes, other criteria (such as performance) can take priority, and having derived state be automatically calculated may not provide the control needed to maintain the user-experience you require. Later on in the series, I’ll provide more complex examples that show where it is easier, or required, to choose to manage derivable state in a reducer. For now, just know that you may encounter a situation where it will feel intuitive to stray from the pattern and that’s fine as long as you understand the trade-offs.

Conclusion

In this article we covered the differences between essential and derived state and how derived state should be modeled in a Redux environment. Derived state takes a bit of work to model well, so it’s important to to be mindful of how exactly memoization is handled in your chosen framework. While all the provided examples were written using reselect, the concepts should apply to any framework, regardless of how much it handles for you automatically. In the next part of the series, we’ll cover how to model side-effects, such as API calls, in your code.

--

--

Mike Yoon
The Startup

Software engineer with over 15 years of experience. Passionate about frontend state management and static typing, but also write about my other interests.