MobX 4: Better, simpler, faster, smaller

..and still, MobX 5 will be even more awesome 😺

I’m proud to announce that a new major version of MobX has been released! The changelog is quite long and includes a migration guide. So, in this blog post I’m just going to highlight the most compelling new features. Btw, if you are not familiar with MobX yet, make sure to check the free egghead.io course!

TL;DR:

Decorators without decorator syntax

Dynamically extend observable objects

await when and flow to further simplify asynchronous processes

and to further simplify asynchronous processes onBecome(Un)Observed to automatically fetch data sources

to automatically fetch data sources Dedicated production build. Smaller & faster.

All of the new features mentioned in this blog post are demonstrated in the code sandbox embedded below. So make sure to check it out!

Decorators without decorator syntax

Most MobX users use the stage-2 decorator syntax, since it results in easy, readable, declarative code. However, others are hesitant to use decorators, as the spec has not been standardized yet (a lot of progress is being made though. The spec is really promising!). For example, create-react-app doesn’t support decorators out of the box for good reasons.

MobX 4 introduces a new api, called decorate , that allows you to use decorators, but without the decorator syntax. It is easier to show than to explain, so here is what that looks like:

Decorators can now be used with- and without decorator syntax enabled

The advantage is that decorators can now be used in other places in the MobX API as well. In the past, specific behavior could be attached to observable properties by passing “magic” wrapper objects like observable.ref(some value) to observable.object or extendObservable. But in MobX 4 you can simply pass a set of decorators as an additional argument.

This brings the same level of flexibility regardless of decorator syntax usage. Syntax is really the only difference now. The new API also makes it possible to use less common object extension methods, like using Object.create to declare prototypes with observable properties.

Dynamic objects

MobX 4 is a stepping stone to MobX 5, which will change the implementation of the observable objects and arrays to use Proxies, removing the known caveats which are a result of the limitations of ES 5. For that reason, the goal of MobX 4 is to be as expressive as MobX 5 will be. Undoubtedly, people will need to downgrade in the future from 5 to 4 because their apps have to run unexpectedly on old browsers like Internet Explorer (which doesn’t support Proxies).

For that reason, in Mobx 4, observable objects can be used as dynamic collections! To achieve that, MobX 4, inspired by Vue, introduces a set of utility API’s to interact with collections:

keys(object) returns the names of all observable properties of an object, similar to Object.keys(object) .

returns the names of all observable properties of an object, similar to . values(object) return the values of all observable properties of an object.

return the values of all observable properties of an object. set(object, key, value) or set(object, keyValueMap) updates or adds all specified properties to an object.

or updates or adds all specified properties to an object. remove(object, key) . Take a guess.

. Take a guess. get(object, key) . Returns the value of an object under the specified key. You probably won’t need this often, but the advantage of this API is that it can track values of not yet existing properties!

. Returns the value of an object under the specified key. You probably won’t need this often, but the advantage of this API is that it can track values of not yet existing properties! has(object, key) . Returns whether key is an observable property. Again, it will track this existence, so you can check for not yet existing properties.

These functions work for maps and arrays as well. Note that you don’t have to use these utilities. But if you use keys or values to iterate objects, and set to dynamically add properties, MobX is able to track and react to any future property additions.

By the way, personally I recommend to still use observable maps for dynamic collections, it is just neater type-wise. And it communicates the intent of your data structures more clearly to others.

Another notable change is that observable maps are now backed by real Maps, so that means that observable now support arbitrarily values as keys, not just strings or numbers like in MobX 3.

Improved support for asynchronous processes

The fact that MobX works out of the box with asynchronous processes has always been one of it strong suits. Nonetheless, MobX 4 makes some common patterns a bit easier.

For example, two utilities of the mobx-utils package, whenWithTimeout and whenAsync have now been merged into the core API. This means that when you create a when reaction, but don’t specify an effect, it will automatically return a promise which you can await . Optionally, you can specify a timeout for this promise and even .cancel() it!

When using MobX in strict mode, asynchronous processes with lots of callbacks can become boilerplaty, as every callback that changes the state need to be wrapped in an action (or use runInAction ). For that reason, the utility asyncAction has been moved from the mobx-utils package to the core package, where it is exposed as flow .

flow uses generators under the hood. That might look a bit scary, but it is semantically equivalent to async / await . Just replace async fn() {} with *fn() {} and await <promise> with yield <promise> . The City example below nicely demonstrates this.

The advantage of using generators is that we can wrap every next chunk of the process automatically in an action for you. Some fun facts: flows are cancellable (just call .cancel() on the returned promise), and support async generators, e.g. flow(async *function() { }) .

For more info on flow you might want to check out this video / article by @leighchalliday.

Note that it is still the best practice to postpone mutations to the end of a process as much as possible, to minimize the amount of different states your application can be in during an asynchronous process.

Resource management

Some long awaited hooks have finally been introduced! onBecomeObserved and onBecomeUnobserved . These hooks can be used on any observable collection or property and make it possible to detect when MobX starts or stops tracking an observable. You can use this feature to, for example, automatically start tracking remote data sources, but only when a components is interested in a value.

The example below automatically fetches the weather for a bunch of city’s, and refreshes that information every few seconds. However, because the demo uses pagination, we only want to fetch the data for cities that are currently being displayed. Which can easily achieved by leveraging these hooks.

The snippet below is the entire definition of the City class. Note that it leverages quite a few new MobX features:

A flow to create an asynchronous process that fetches the weather data and handles errors.

to create an asynchronous process that fetches the weather data and handles errors. decorate to mark the necessary fields as observable or flow without needing decorator syntax support.

to mark the necessary fields as or without needing decorator syntax support. onBecomeObserved / onBecomeUnobserved will cause the data subscription to the weather API to automatically resume / suspend as soon something / nothing is interested in the temperature attribute.

So, thanks to all that, our CityView component isn’t even aware of the data fetching that is required, and becomes trivial. No mounting logic required! Also note that simply removing city.temperature from this component, would cause the City class to not start data fetching at all 🎉

Faster & smaller

MobX 4 introduces separate builds for production and development. This makes it possible to make the production build of MobX smaller and faster, by stripping a lot of consistency and API usage checks. To get a production build, use the .min.js version from the CDN, or substitute process.env.NODE_ENV with "production" while bundling. This is exactly the same mechanism as React uses, so if you have a proper React build setup already, this does not require any further effort!

Anyway, some stats:

The test suite performance improved with 14% between MobX 3 and 4.

The gzipped minified build size dropped from 17.3kB to 14.2kB (22% smaller). But, in practice this will shave off even a bit more, as MobX 4 is better tree-shake-able.

…and more!

That’s not all folks! But these are the most interesting pieces. But do check the full changelog for additional hidden gems.

For big improvements, one needs Focus :) monkeyuser.com

MobX is an open source project. And although Mendix is so generous to sponsor the maintenance of MobX, I actually took a few weeks off to work on MobX 4 and 5 (which hopefully follows in weeks). The main reason: being able to work on MobX without any distraction, to make it possible to make some big improvements!

So, if MobX saved you tons of effort and hence money in the past. Consider sponsoring it’s development! Thanks! 🍻

— -

Header photo by Photo by Digital Buggu from Pexels https://www.pexels.com/photo/abstract-art-artistic-artwork-368774/