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
andflow
to further simplify asynchronous processesonBecome(Un)Observed
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:
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 toObject.keys(object)
.values(object)
return the values of all observable properties of an object.set(object, key, value)
orset(object, keyValueMap)
updates or adds all specified properties to an object.remove(object, key)
. 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!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 anaction
(or userunInAction
). 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. decorate
to mark the necessary fields asobservable
orflow
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 thetemperature
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.
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! 🍻
- MobX on open collective
- Patreon
- Paypal (yes, Marinus Cornelis is my legal name 🎃)
— -
Header photo by Photo by Digital Buggu from Pexels https://www.pexels.com/photo/abstract-art-artistic-artwork-368774/