Effective MobX Patterns (Part 2)
In the previous part we looked at how you can setup a MobX state tree and make it observable. With that in place, the next step is to start reacting to changes. Frankly this is where the fun begins!
MobX guarantees that whenever there is a change in your reactive data-graph, the parts that are dependent on the
observable properties are automatically synced up. This means you can now focus on reacting to changes and causing side-effects rather than worrying about data synchronization.
Let’s dig in and see the various ways in which you can cause side-effects.
@action as an entry point
By default when you modify observables, MobX will detect and keep other depending observables in sync. This happens synchronously. However there may be times when you want to modify multiple observables in the same method. This can result in several notifications being fired and may even slow down your app.
A better way to do this is to wrap the method you are invoking in an
action(). This creates a transaction boundary around your method and all affected observables will be kept in sync after your action executes. Note that this delayed notification only works for observables in the current function scope. If you have async actions which modify more observables, you will have to wrap them in
Actions are the entry points into mutating your stores. By using actions, you can make updates to multiple observables into an atomic operation.
@actionmethods that do this change for you. In fact, you can make this mandatory by setting
autorun to trigger side-effects
MobX ensures that the graph of observables always stays consistent. But if the world were only about observables, it would be no fun. We need their counterparts: the observers to make things interesting.
In fact, the UI is a glorified observer of your MobX Stores. With mobx-react you get a binding library that makes your React Components observe the store and auto-render themselves whenever the stores change.
However, the UI is not the only observer in your system. You can add many more observers to your stores to do a variety of interesting things. A very basic observer could be a console-logger that simply logs the current value to the console when an observable changes.
autorun we can setup these observers very easily. The quickest way is to supply a function to
autorun. Whatever observables you use inside this function are automatically tracked by MobX. Whenever they change, your function will re-execute (aka autorun)!
As you can see in the log above,
autorun will run immediately and also everytime there is a change to the tracked observables. What if you don’t want to run immediately, and instead run only when there is a change? Well, you have
reaction just for that. Read on.
reaction to trigger side-effects after first change
reactions provide more fine grained control compared to
autorun. First, they don’t run immediately and wait for the first change to the tracked observables. Also the API is slightly different than
autorun. In the simplest version you provide two inputs:
The first function (the tracking function) is supposed to return the data that will be used to track. This data will then be passed into the second function (the effect function). The effect function is not tracked and you are free to use other observables here.
reaction won’t run the first time and will wait for a change from the tracking function. Only when the data returned by the tracking function changes, will the side effect be executed. By breaking the original autorun into a
tracking function +
effect function, you get more control around what is used for actually causing the side effect.
In the example above, I don’t want a navigation when I load the ‘main’ page. A perfect case for using
reaction. Only when the
page property of the
Router changes, will the navigation happen to the specific url.
The above is a really simple Router with a fixed set of pages. You could make this more extensible by adding a map of pages to URLs. With such an approach, routing (with URL changes) becomes a side-effect of changing some property of your store.
when to trigger one-time side-effects
reaction are long-running side-effects. You will create such side-effects when you initialize your application and expect them to run during the lifetime of your application.
One thing I didn’t mention before is that both these functions return a disposer function. You can call the disposer and cancel the side-effects any time.
Now the apps we build have a variety of use cases. You may want certain side-effects to run only when you reach a certain point in your application. Also you may want these side-effects to only run once and then never again.
Let’s take a concrete example: Say, you want to show a message to the user when they reach a certain milestone in the app. This milestone will only happen once for any user so you don’t want to setup a long-running side-effect like
reaction. It’s time you pull out the
when API to do this job.
when takes two arguments, just like
reaction. The first one (the tracker-function) is supposed to return a boolean value. When this becomes true, it will run the effect-function, the second argument to
when. The best part is that it will auto-dispose the side-effect after it has run. So there is no need to keep track of the disposer and manually call it.
Although I have only mentioned about
when, there are few more APIs that MobX offers for some advanced use cases. I am choosing to deliberately ignore them for now as the bulk of MobX is really about using the above mentioned APIs. Once you become comfortable with these APIs, the rest is easier to follow and grasp. This is the foundation and we have to make this second nature before we start constructing advanced and fancy structures atop.
We will have a separate post about those advanced APIs. A Part-4 perhaps :-)
A Powerful toolset
So far, we have seen a variety of techniques to track changes on an object graph and also react to those changes. MobX has raised the level of abstraction so that we can think at a higher-level without worrying about the accidental complexity of tracking and reacting to changes.
We now have a foundation on which we can build powerful systems that rely on changes to a domain-model. By treating everything outside the domain-model as a side-effect, we can provide visual feedback (UI) and perform a host of other activities like monitoring, analytics, logging, etc.
To be continued…
In Part 3, we will look at a variety of examples to apply all that we learnt so far in practice.
Huge thanks to Michel Westrate (author of MobX) for reviewing the content.