MobXSneak Peek Behind the Scenes of MobX
Pau Ramon Revilla
Stemming from the previous three articles on MobX, the following facts can be highlighted about MobX:
- It is minimalistic.
- It supports the mutation of states through actions.
- It supports reactive programming or is built around reactive programming.
In this chapter, we would focus on the last two points. Why is mutation allowed in MobX, though it is considered an anti-pattern? How does Mobx handle this mutation? What is reactive programming in the MobX context? As we try to answer this, let’s start by looking at data from the perspective of identity and equality.
Identity and Equality in Data
Data can be categorized either as primitives or data structures. Primitive data types are numbers, booleans, and other simple data types.
For primitives, identity is equal to equality. That is,
1 is equal to
true is equal to
“Tayo” is equal to
“Tayo”. In the same vein,
“Tayo” is not equal to
“Oyat”, as the same characters are spelled backward. So for primitives, identity and equality are simple and straightforward.
However, with data structures, it’s a bit more complicated.
The log above will always return false. This is because when we compare the identity of data structures, we are comparing two pointers as succinctly explained in this article. This difference between identity and equality is solved in some programming languages with immutable data structures. Additionally, it can be solved using the reactive programming paradigm (on which MobX is based on). Therefore, how does MobX solves this problem?
Preparing State for Subscription Using Observable
observable, as you might have noticed from the previous articles in this series, wraps data and gives them observability. That is,
observable makes data available for subscription.
Next, we need to be able to listen to the changes to the state and this is possible through
autorun, more about it below.
Subscribing to State Changes
To ensure that we listen to or subscribe to changes to the state, we would need
reactions. One of the two common reactions in MobX is
runInAction. In our case, since we have prepared
studentA with the observable wrapper, we can listen to changes to it with the
With the above setup, if we try to change the
studentA, we should get the following:
As seen above,
autorun, having been subscribed to
name, has detected a change to the
name prop. It is interesting to know that
autorun will only detect changes to what it is listening to. MobX subscriptions work by reading data. It assumes that if you have read a piece of data, it means you are interested in hearing more about it. This automatic subscription feels like magic or wrong but it is actually the secret sauce to MobX. Also, this ensures that you don’t over or under-subscribe (like it happens with redux or with hooks dependencies):
This subscription is synchronous. This means that the
autorun is triggered immediately after every change to the subscribed state. To understand, consider the following:
Asynchronous State Subscription
As shown above,
autorun works synchronously. Now imagine that I made changes to the
name of the student, and then changed it back to the original state. Ideally, we should have our component rendered twice, but that is not what happens. So how does MobX prevent this behavior?
runInAction is asynchronous. Consider the following:
runInAction, the log only prints the last change to the name, unlike
autorun which does it synchronously. This is particularly useful, for example, when you want to have transaction semantics, this means, batching several mutations and only reacting once. In a react application, this can save you lots of re-renders and you may want to avoid intermediate states re-rendering the component.
So far, we have only seen side effects. The biggest advantage of using any reactive framework is having derived computations, which is just a fancy word for caching if you think about it. Did I ever mention that cache invalidation is hard? Well, caching is a side-effect, and we can see that MobX is good and efficient in this regard. Let's see it in action:
As we can see in the example above,
computed returns an observable. This means that you can combine
computeds and only read them whenever you want the reaction to happen.
There is only one gotcha: MobX will only cache
computeds when they are inside a reaction. MobX will clean the subscriptions for you automatically when leaving the reaction. This is why in the example above, I added the
keepAlive: true, which ensures that the cache will leave forever. This is usually not a problem when using it in conjunction with React, as we will see later on.
MobX is sweet! We know that state mutation is possible within MobX and have established tools like
runInAction which MobX uses behind the scenes to handle state mutation effectively following the reactive programming paradigm. It should not be surprising that we use MobX at Factorial 😉 so in the last part of this series 😅, I will be highlighting how MobX ranks when compared to Redux, hence the reason why we use it.