Auto-save

A common need is to launch some process whenever any property in an observable object changes its value (or a property is added). This can be done with:

autorun / autorunAsync

// Mobx 2.x: import { autorun, observable, asMap } from 'mobx';
// Mobx 3.x:
import { autorun, observable} from 'mobx';

// Mobx 2.x: const myObservableMap = observable(asMap({......}));
// Mobx 3.x:
const myObservableMap = observable.map({......});

autorun(() => {
  if (Array.from(myObservableMap).length) {
    // do something, then perhaps myObservableMap.clear() when done
  }
});

The above example uses an observable map and the autorun function will react whenever map entries are added, removed or changed. Note that you must use a map to react to addition of keys, a plain object won't work even if you use extendObservable to add properties to it. The autorun function will execute when the above code is initially executed, so its if condition must work for both the initial state and for subsequent changes. Any observable properties accessed during autorun's execution will cause it to be re-fired if they change in the future. However, asynchronous code and code within an action or transaction is not tracked by autorun.

reaction

import { reaction, observable } from 'mobx';
import _ from 'lodash';

class MyClass {
  @observable myObservable = {......}
}
const obj = new MyClass();

reaction(
  () => Object.values(obj.myObservable),
  _.debounce(() => {
    // do something
  }, 1000)
);

The above example uses an observable object and a reaction and often provides for a clearer approach than autorun. Whenever the first function return value changes (in the above example that will happen whenever any property in the observable object changes its value), the second function is invoked. The return value of the first function is actually passed to the second function but you often don't need it, so it's not used above. Although the above reaction won't fire if you add a property to the object, it will react if you replace the whole object in myObservable with one that has additional properties. You can, of course, use observable maps or arrays with reaction as well. The reaction function is not called when the above code is initially executed.

Note that in the above example, the reaction function is also debounced (using a lodash utility). Although reaction supports a delay parameter, it, just like autorunAsync, actually results in throttling and not debouncing (the mobx documentation is misleading in this regard). With throttling, a function is executed no more often than the specified interval, whereas with debouncing in the above example, the function is executed when observable stops changing for 1 sec.

observe

At first you might think that observe is just like observer in mobx-react but actually mobx-react observer is more like an autorun. observe, on the other hand, is a lower-level and granular facility that provides you with detail of what changed exactly:

import { observe, observable } from 'mobx';

class MyClass {
  @observable myObservable = {......}
}
const obj = new MyClass();

function onChange(change) {
  console.log(change.type, change.name, "from:", change.oldValue, "to:", change.newValue);
}

let dispose = observe(obj.myObservable, onChange);
observe(obj, "myObservable", () => {
  dispose && dispose();
  dispose = observe(obj.myObservable, onChange);
});

In the above example, the onChange function will be called whenever a property of obj.myObservable object changes. It is also set up to 'follow' whole-object replacements, so that if a new object is placed into obj.myObservable, it'll start observing that new object instead of the old one. Note that observe is fired immediately upon a change in a property even if in the middle of a transaction/action, whereas autorun and reaction are fired when the transaction/action completes. Also, calling extendObservable on obj.myObservable will invoke onChange with change.type of 'add' for any newly added properties and changes in their values in the future will result in observe firing. However, be aware that no other observers/autoruns/reactions will pick up on properties added this way. Observable maps also have an .observe(onChange) method that works just like the above observe(obj.myObservable, onChange).

In our MobX FormStore, we use observe to track changes to an observable object, and accumulate them in an observable map, which in turn has an autorunAsync checking its keys like in the first example to implement auto-save of changes every 30 seconds.

Last updated