Use extendObservable sparingly

extendObservable can be used to add new observable properties to an object BUT if this is done after some observer/reaction/autorun accesses the object, these reactions will not fire / update the UI. The biggest problem with extendObservable is that it can often give the appearance of working but only by accident.

Consider this example:

import { autorun, observable, extendObservable } from 'mobx';

class MyClass {
  @observable myObservable = { key1: 'value1' }
}
const obj = new MyClass();

autorun(() => {
  console.log(obj.myObservable.key1); // -> first run: value1
  console.log(obj.myObservable.key2); // -> first run: undefined
});

extendObservable(obj.myObservable { key2: 'value2' }); // no-effect on autorun or observer
obj.myObservable.key2 = 'newvalue2'; // no-effect on autorun or observer

obj.myObservable.key1 = 'newvalue1'; // autorun reacts and will log newvalue1 and newvalue2
obj.myObservable.key2 = 'new'; // autorun reacts and will log newvalue1 and new
// - from now on, it will appear that key2 is perfectly 'observable'
//   but it's by accident of having key1 modified first!

Having code that depends on sequence of events is very error-prone and hard to reason about.

Therefore, the only time when it's ok to use extendObservable is during instance construction / module initialization before ANY observers/autoruns access the object in question.

Otherwise, the only correct way to add new observable properties to an observable object is to replace the whole object, i.e.:

obj.myObservable = { key1: 'value1', key2: 'value2' };
// OR:
obj.myObservable = Object.assign({}, obj.myObservable, { key2: 'value2' });

Object.assign is also required instead of a simple assignment if the new object is not a plain object but is constructed from a class (i.e. has a prototype other than Object).

If your object can exist in one of several states and its properties are known in each case, you can generally manage this with whole-object replacement. But if you need truly dynamic observable properties, then you need to use an observable map, not an object. With an observable map you can use its .merge() or set() methods to add new properties to it at any time. But keep in mind that if you place a reference to an object in a key in the observable map, new keys in that object are still not going to be 'noticed' by existing reactions. So, instead of adding keys to that object, you'll need to create a new object and .set() it into your map.

If you are working with observable objects, always remember this from https://mobx.js.org/best/react.html:

MobX reacts to any existing observable property that is read during the execution of a tracked function.

Last updated