4th May 2021
Manage state outside of the view layer with ease
MobX is a simple, scalable, boilerplate-free state management solution. It allows you to manage application state outside of any UI framework, making the code decoupled, portable and, above all, easy to test.
It implements observable values, which are essentially using the publish/subscribe pattern. They differ in that the subscriptions are handled automatically. The engine tracks where you used the observables and only re-executes the side effects, like a render, if they change.
Here is a simple counter app. We will use a class to organise our state. This is the most flexible way to use MobX. You are not required to do so, but we will explain that later.
You can find this example and more advanced ones in the demo repository.
You can also see that we didn’t need to explicitly specify the component’s dependencies from the store. MobX tracks them automatically and builds a dependency graph that captures all relations between state and output. This guarantees that your React components render only when strictly needed. This is a very powerful feature that spares you from using error-prone techniques such as memoization or selectors. Performance concerns are a thing of the past.
Observables represent the reactive state of your application. They can be simple scalars, objects, arrays, maps, sets or even custom defined ones. Any change to this data triggers reactions, which in turn trigger side-effects, such as a React render. The reactivity system will make sure that only relevant dependencies are updated.
To make a value observable, we need to mark it as such so that MobX will track it. There are multiple ways to do that, as shown below.
Computed values are used to derive information from other observables. Conceptually, they are similar to formulas in a spreadsheet. They differ from other libraries in that they are automatically memoized and hence update only when their dependencies change. They help us store a minimum amount of state and keep the logic decoupled from the view layer. To create a computed value, we use a
get function and mark it with
Actions are functions that modify the state. Any code can do so, but actions have performance optimisations and keep the code decoupled. They run in transactions, meaning no observers will be updated until the outermost action has finished. This will ensure that intermediate changes to state do not trigger unnecessary re-renders.
Furthermore, they can be asynchronous without any special work.
Now that we understand the core concepts, we can reduce the necessary boilerplate to a single function call using
makeAutoObservable(). This lets MobX figure out the annotations behind the scenes automatically. Using it, the store to our counter app is reduced to this:
Reactions are what trigger the reactivity in the system. This is where the magic of MobX comes together. A reaction implicitly tracks all the observables being used and then re-executes the side effect whenever the dependent observables change.
They come in multiple flavours —
observer (in React).
The most used reaction is the
observer, which tracks observables in a React component. Internally, it will figure out which observables are being used and then re-render the component only when strictly necessary. As mentioned earlier, this is one of the most powerful features of MobX. It makes your app highly performant without any effort on your part.
Autorun, reaction and when
Other reactions should be used sparingly — only when you want to produce a side-effect and there is no direct relation between cause and effect. They all work the same but differ in the level of granularity you can specify in the dependencies.
Autorun is the simplest one and will figure out the dependencies automatically.
Reaction lets you specify the dependencies with a predicate.
When is similar to Reaction but only runs when the predicate returns true.
computedis a reaction too. The difference is that it produces a value, whereas other reactions produce a side-effect. It is therefore called a derivation.
Usage without classes
MobX can be easily used without classes. There are some advantages to using classes such as inheritance, but you may use the following syntax:
MobX works outside of any UI framework, so it is not bound by their programming model limitations. The advised way to use MobX is to use classes and OOP, which unlocks incredible productivity and easy code reuse. The example here can be reconstructed using other state solutions, but MobX’s minimalistic boilerplate makes it especially easy..
A common issue with larger, dashboard-style applications is managing collections of data. It usually involves some fetching, filtering, pagination and sorting. Writing the same code over and over for each page can be cumbersome and tedious. With MobX, it’s straightforward to create a reusable and configurable module that handles all these common tasks. The code for this is too long to include here, but you can check it out in the demo repository.
From this comprehensive review of MobX, we can see that it has no boilerplate — just some
reactions. The reactivity is handled by the engine, and it just works. Some skeptics describe it as too “magical”, but once you start using it you will see that this is not the case. You can learn more about the inner workings in the official documentation here: Understanding reactivity in MobX.
The simple API also means that MobX is un-opinionated about how you actually structure your stores. This can be a bit of a double-edged sword. For larger projects, proper engineering practices and planning need to be applied. Interestingly, most users end up converging to a similar structure, also described in the official documentation – Defining data stores in MobX.
Opinionated projects have been built on top of the MobX engine. The most notable one is mobx-state-tree. It combines the best of both worlds — MobX reactivity with Redux-like tree structure, transactions and time travel. Mobx-keystone is similar. However, in both cases you lose some of the flexibility of pure MobX.