Mobx 'asFlat', 'asReference' not working as expected

Viewed 443

We are using Mobx in one of our projects, and I see frequent use of asFlat and asReference in the code. As per my understanding, asReference fires a change only when the reference is changed but not when any property on the object changes. In contrast, Mobx will track all properties of an object marked as observable (given that those properties were present during object creation). But I don't see any change in behaviour between the two in the code below.

Similarly, asFlat allows the property itself to be observable but not any of its children. Which means that adding a new value to an array marked as asFlat will not fire a change, but changing the reference of array will. However, in the code below, both arrays behave exactly the same. Why is this happening?

Also, what is the difference between asReference and asFlat, as both seem similar?

  const {observable, asFlat, asReference, reaction} = mobx;

  class Car {

    constructor() {
      reaction(() => this.colors, () => console.log('colors changed: ',this.colors));
      reaction(() => this.colorsAsFlat, () => console.log('colorsAsFlat changed: ',this.colorsAsFlat));
      reaction(() => this.config, () => console.log('config changed: ',this.config));
      reaction(() => this.configAsReference, () => console.log('configAsReference changed: ',this.configAsReference));
    }

    @observable colors = [];
    @observable colorsAsFlat = asFlat([]);
    @observable config = {model : '7 Series'};
    @observable configAsReference = asReference({model : '7 Series'});
  }

  const bmw = new Car();

  bmw.colors.push('Red');
  bmw.colors[1] = 'Gray';
  bmw.colors = ['Black'];
  bmw.colors.push('Blue');

  bmw.colorsAsFlat.push('Red');
  bmw.colorsAsFlat[1] = 'Gray';
  bmw.colorsAsFlat = ['Black'];
  bmw.colorsAsFlat.push('Blue');

  console.log("Initial Config: ", bmw.config);

  bmw.config.model = '5 Series';
  bmw.config.autopilot = 'true';
  bmw.config = { sunroof : 'true'};

  bmw.configAsReference.model = '5 Series';
  bmw.configAsReference.autopilot = 'true';
  bmw.configAsReference = { sunroof : 'true'};

reaction is just used to track changes in the observables and will log the observable property whenever it changes. Here is a fiddle for the same. Observe the console output for this code.

Questions from above code:

  1. Why bmw.colors.push('Red') and bmw.colors[1] = 'Gray' did not fire a change (reaction)?
  2. bmw.colors = ['Black'] fired the reaction but why does the colors array also includes 'Blue' in it in the log when 'Blue' is assigned after 'Black'?
  3. colorsAsFlat is firing on same changes as colors even though it is declared asFlat. So it should only fire on bmw.colorsAsFlat = ['Black'] as its a refernece change (and it does so), but it also unexpectedly includes 'Blue' color. Why so?
  4. Why does logging bmw.config print Initial Config: {autopilot: "true", model: "5 Series"} when these two properties are assigned after the console.log statement?
  5. Why doesn't reaction fire for bmw.config.model = '5 Series'?. Aren't object properties that were present when the observable object was created observed for changes?
  6. configAsReference is behaving as expected and is firing a change only when the reference is changed; as the reaction fires only on bmw.configAsReference = { sunroof : 'true'}, which is a reference change.
1 Answers
  1. The reaction will only fire if the data function's return value changes. The default comparison will effectively just do this.colors === this.colors and therefore the effect will not run. Try returning this.colors.length instead from your data function.

  2. Mobx will batch updates in 'transactions' for reasons of performance, so you won't necessarily see a reaction run after every change where multiple happen in the same block of code.

  3. Normally when you add elements to an observable array, the new elements are made observable themselves. Using asFlat disables this behaviour, but will be unnoticeable when your array elements are just strings.

  4. I'm guessing that you're just looking at the expanded object as logged in your console, which probably shows the current value of the object, rather than the value of the object when it was logged. Try logging the individual keys and you should see the values as they were at the time of the logging.

  5. Same issue as for question (1). The simple comparison will show no changes - update the data function returning this.config.model and you should see a reaction.

  6. Not a question.

The Mobx docs for reactions have some good information. asReference and asFlat go away in more recent versions, but are equivalent to observable.ref and observable.shallow. See the docs for more information.

Related