Due to popular demand (and to have a cool story for my grand-children), these are the inner workings of MobX. A lot of people are surprised how consistent and fast MobX is. But rest assured, there is no magic in play!

First, let’s define the core concepts of MobX:

Observable state. Any value that can be mutated and might serve as source for computed values is state. MobX can make most types of values (primitives, arrays, classes, objects, etc.) and even (potentially cyclic) references observable out of the box. Computed values. Any value that can be computed by using a function that purely operates on other observable values. Computed values can range from the concatenation of a few strings up to deriving complex object graphs and visualizations. Because computed values are observable themselves, even the rendering of a complete user interface can be derived from the observable state. Computed values might evaluate either lazily or in reaction to state changes. Reactions. A reaction is a bit similar to a computed value, but instead of producing a new value it produces a side effect. Reactions bridge reactive and imperative programming for things like printing to the console, making network requests, incrementally updating the React component tree to patch the DOM, etc. Actions. Actions are the primary means to modify the state. Actions are not a reaction to state changes but take sources of change, like user events or incoming web-socket connections, to modify the observable state.

Computed values and reactions are both referred to as derivations in the remainder of this blog-post. So far, this might all sound a bit academic so let’s make it concrete! In a spreadsheet all data cells that have values would form the observable state. Formulas and charts are computed values that can be derived from the data cells and other formulas. Drawing the output of a data cell or a formula on the screen is a reaction. Changing a data cell or formula is an action.

Anyway, here are all four concepts in a small example that uses MobX and React:

Listing 1: Observable state, computed values, reactive Reactjs component and some actions

We could draw a dependency tree based on the above listing. Intuitively it will look as follows:

Figure 1: Dependency tree of profileView component. FullName is in reactive mode, actively observing firstName and lastName.

The state of this applications is captured in the observable properties (blue). The green computed value fullName can be derived from the state automatically by observing the firstName and the lastName. Similarly the rendering of the profileView can be derived from the nickName and the fullName. The profileView will react to state changes by producing a side effect: it updates the React component tree.

When using MobX the dependency tree is minimally defined. For example, as soon as the person being rendered has a nickname, the rendering will no longer be affected by the output of the fullName value, nor the first- or lastName (see listing 1). All observer relations between those values can be cleaned up and MobX will automatically simplify the dependency tree accordingly:

Figure 2: Dependency tree of the profileView component if the user has a nickname (see listing 1). In contrast to figure 1, fullName is now in lazy mode and does not observe firstName and lastName

MobX will always try to minimize the number of computations that are needed to produce a consistent state. In the rest of this blog post, I will describe several strategies used to achieve this goal. But before diving into the magic of how computed values and reactions are kept in sync with the state, let’s first describe the principle behind MobX:

Reacting to state changes is always better then acting on state changes.

Any imperative action that an application takes in response to a state change usually creates or updates some values. In other words, most actions manage a local cache. Triggering the user interface to update? Updating aggregated values? Notifying the back-end? These can all be thought of as cache invalidations in disguise. To ensure these caches will stay in sync, you need to subscribe to future state changes that will enable your actions to be triggered again.

But working with subscriptions (or cursors, lenses, selectors, connectors, etc) has a fundamental problem: as your app evolves, you will make mistakes in managing those subscriptions and either oversubscribe (continue subscribing to a value or store that is no longer used in a component) or undersubscribe (forgetting to listen for updates leading to subtle staleness bugs).

In other words; when using manual subscriptions, your app will eventually be inconsistent.

Figure 3: Inconsistent Twitter page after updating profile. The pinned tweet is displaying stale values for both the name and the profile picture of the author.

The above image is a nice example of the Twitter UI being inconsistent. As explained in my Reactive2015 talk, there can only be two causes for this: Either there is no subscription that tells tweets to re-render if the profile of the associated author has changed. Or the data was normalized and the author of a tweet doesn’t even relate to the profile of the currently logged-in user, despite the fact that both pieces of data try to describe the same properties of the same person.

Coarse grained subscriptions like Flux-style store subscriptions are very susceptible to oversubscribing. When using React, you can simply tell whether your components are oversubscribing by printing wasted renderings. MobX will reduce this number to zero. The idea is simple yet counterintuitive: More subscriptions result in fewer recomputations. MobX manages many thousands of observers for you. You can effectively tradeoff memory for CPU cycles.

Note that oversubscribing also exists in very subtle forms. If you subscribe to data that is used, but not under all conditions, you are still oversubscribing. For example, if the profileView component subscribes to the fullName of a person that has a nickName, it is oversubscribing (see listing 1). So an important principle behind the design MobX is:

A minimal, consistent set of subscriptions can only be achieved if subscriptions are determined at run-time.

The second important idea behind MobX is that for any app that is more complex than TodoMVC, you will often need a data graph, instead of a normalized tree, to store the state in a mentally manageable yet optimal way. Graphs enable referential consistency and avoid data duplication so that it can be guaranteed that derived values are never stale.