Many words, talks and articles have been dedicated to the question “should I be using Redux or MobX?” Even React books tend to discuss both nowadays. Yet there is no ‘winner’ and nor will there be (off-topic: it is even highly questionable if there is anything to ‘win’ in OSS in the first place). The reason for this is simple, both libraries yield completely different benefits. So your preference will be determined by your requirements and values rather than the intrinsic quality of either of them.

Now, since that has been settled, let me share why I like Redux.

First of all, state within Redux is simply represented as plain objects. Nothing more. Redux makes state tangible. You can pick up objects and drop them in a different place. Like in local storage or in the body of a network request.

Secondly, Redux offers many constraints that make it easy to write generic algorithms that reason about the state without specific domain knowledge (unlike reducers). Such algorithms are typically expressed as middleware. Creating such mechanisms is feasible, partly because the data is immutable, but mostly because the state is tree shaped and preferably serializable. This makes it possible to traverse any Redux state tree in a predictable manner.

The limitations of MobX

MobX simply can’t offer these benefits as it’s unopinionated regarding the architecture of your data. It does not make assumptions on whether your data is cyclic or not. It does not make assumptions on whether you use plain objects or classes. It doesn’t assume anything about the stuff you store. So for these reasons MobX cannot guarantee your data is JSON serializable, or whether it can be traversed in finite time.

Probably, it is more accurate to talk about MobX as a data flow library, that enables you to roll your own state management architecture with minimal effort. The big upside of course is that it can be adopted in most existing code bases without requiring a full rewrite. For example creating bidirectional bindings with older stacks like Backbone or Knockout are not uncommon. Anyway, the interesting question is:

What if MobX did make assumptions on how your data is structured? What if it is opinionated? That is the curious case of Mobx state tree.

A mutable immutable tree

The idea behind Mobx-state-tree (MST) is pretty simple:

Keep the ergonomics of stable reference and directly mutable objects. In other words; be able to have a variable pointing to an object, and make subsequent reads or writes to it. Without needing to fear that you’re working with old data. While, in the background,.. State is stored in an immutable, structurally shared tree. That might sound a bit vague, so here is a picture from my React-Europe talk on MST explaining the concept better:

This automatically generated immutable tree in MST is called a snapshot. And obtaining one is free as they will be generated automatically (and efficient, thanks to the backing MobX computed expressions that generate them (how?)). However, snapshots are not the primary means in which you interact with a tree. They are just available to make it simple to solve generic problems like time travelling.

Interaction with the tree primarily happens in the classic OO way: Just modify properties of instances you have a reference to. A new immutable tree is constructed for you in the background, you don’ t have to do that yourself.

Of course you can update the tree by applying a more recent snapshot. When a snapshot is applied to an existing tree MST will reconcile as many instances as possible. This process is very similar to how React works: The render method always produces a tree of new element descriptions (like a snapshot); yet not all elements will result in new component instances. React will try to match new element descriptions as much as possible against existing instances, to preserve the local state of components and reduce the amount of changes to the DOM.

So, by limiting state to be a tree of serializable values, and by backing it with a structurally shared immutable tree, MST makes sure state is tangible.

Types and boilerplate free object manipulation

Now, since MST provides stable instances; we can pull in many convenient features from OOP architectures. For example all nodes in a MST tree have a type. Types enable MST to perform runtime type checking. Types describe the shape of data. And it allows for example TypeScript to provide design time type checking as well. Enabling auto completion, easy feature discovery etc.

TypeScript can actually design-time type check MST types!

But types enable more: Types can expose the operations (actions) that can be invoked on a type instance. They can expose automatically derived views on the data derived (a.k.a. MobX computed values), as-if they are part of the state. This greatly helps in the discoverability of your state, and allows for nice co-location of types and operations on the type.

Types can even declare lifecycle hooks, similar to React components. They enable you to declare what should happen when data enters or leaves a state tree. This is invaluable when you need to manage resources that are associated with your state like WebSocket connections or pending network requests.

The type system is also used by the MST reconciler to determine which type of instances should be instantiated given an arbitrarily snapshot. This makes it trivial to support Hot Module Reloading (HMR).

The type system in MST is strongly inspired by tcomb (and largely written by the awesome Mattia Manzati, the co-author of MST). It doesn’t offer inheritance, but it does offer more powerful type composition techniques like type composition, unions, typed arrays, maps, enumerations etc.

Example definition of some MST types

Even “foreign” references are a first class concept in MST. This means that although your data is always stored as a directed tree, you can interact with it as-if your data is cyclic. For example you can write expression like: assertTrue(person.classMates[0].classMates[0] === person) . It looks cyclic, but it isn’t. It’s just MST taking care of reference types (yep, you can safely assign values that way as well).

In other words, although there is an immutable tree behind the scenes, the developer interaction with the state is as comfortable and to-the-point as in any OO approach. Exactly what people love about MobX. No please-create-a-new-state-tree-for-me boilerplate.

Patches, asynchronous processes & generic algorithms

But MST has some other nice tricks up it’s sleeve. For example, because MST is powered by MobX behind the scenes, you get the reactivity framework for free. In other words, pass a MST model in to a component. Slap observer on the component, and render the model. MobX will take care of the reactive business of updating the component when relevant model properties have changed. No need to write your own mapping functions for this.

With MST, all state is ultimately a tree where each value resides at an unique path. That, combined with the fact that MobX enables fine grained change notification, enables MST to support JSON patches out of the box. JSON patches are great for (real time) server communication. But even better; MST also generates inverse patches. To see some example patches, just click around in the above example.

Patches form the basis of any proper undo / redo system. (Snapshots don’t support undo / redo well in multi actor systems; they tend to undo everybody’s changes when going back in time. Even changes that were received from the server for example. For a detailed break down on that, check my React Next talk!).

Example of a model containing an asynchronous process (try it below)

Finally, MST offers first class support for middleware. And since asynchronous processes are also built-in (which are, like redux-saga, based on generators) this allows for very powerful middleware. Middleware that can reason about long running processes as well. For example, in my recent React Next talk I wrote middleware that can undo asynchronous processes, even when they are interleaved by other asynchronous processes. By precisely undoing the mutations in that process, while leaving the mutations of concurrently running processes in place. The talk is not yet online, but here are the slides and the middleware example.

Anyway, here is a simple demo from the React Next talk, demonstrating that middleware. To see it in action; just try the sandbox below. If you press the “Full visit” button, a long running process starts, and ultimately it will result in an exception (assuming there are less then 2 pieces of toilet paper in stock. However during this process you can also start dragging the painting around. The middleware will make sure that when the exception eventually occurs, the toilet state is reset to it’s starting values. But the painting that was moved around will stay where you dropped it. It won’t rewind with the other changes. If you are curious to the implementation of the middleware; the implementation can be found here and can be used in any MST project.

The curious case of MobX state tree

Unlike MobX, Mobx-State-Tree is an opinionated library that imposes a strict architecture on state organization.

In return for that you get a system where state is tangible, since it is stored as an immutable, structurally shared tree.

But on the other hand, it also offers the boilerplate free, classical OO way of interacting with your state. This goes hand in hand with a powerful type system and built-in support for asynchronous processes.

On top of that it offers all the essential tools that allows you to write generic mechanisms on top of MST: Snapshots, patches and middleware.

Version 1.0 was released just two weeks ago. So, have fun, ⭐star⭐ it while it is hot and get the best of both worlds! Check if it is the sweet spot between MobX and Redux you were hoping for, and let us know.

Tip: read our introduction tutorial or watch the free egghead.io tutorial to get started!

Cheers!

Michel

…and a big thanks to: Matt Ruby for reviewing this blog post & Mattia Manzati for co-authoring the library!