Mobx: How do I subscribe to @computed object in mobx?

Created on 24 Oct 2019  ·  13Comments  ·  Source: mobxjs/mobx

Question

I want to subscribe to a computed property that returns a simple object.

Codesandbox: https://codesandbox.io/s/epic-noether-g8g0x

I made a simple example of my situation to show exactly what the problem is:

const zeroSize = {
    width: 0,
    height: 0
};

class Item {
    @observable size = zeroSize;
}

class Collection {
    @observable childrens: Item[] = [];

    @computed get size() {
        return this.childrens.length > 0 && this.childrens[0] ? this.childrens[0].size : zeroSize;
    }

    constructor() {
        reaction(() => this.size, size => {
            // Expected { width: 0, height: 1000 }
            // Actual { width: 0, height: 0 }
            console.log('-----size=', toJS(size));
        });
    }
}

const item1 = new Item;
const collection1 = new Collection;
collection1.childrens.push(item1);
item1.size.height = 1000;
❔ question

All 13 comments

Interesting, I don't exactly know the answer, but if you flip those last two lines it works as expected. I am sure someone else can come up with a better one :)

But I do not want to change these lines in places.

I always recommend against using reactions. If you need to run a side-effect, use autorun - it will automatically subscribe to any dependencies needed to run the side effect.


  constructor() {
    autorun(() => {
      console.log('-----size=', toJS(this.size));
    });
  }

But reaction recommends the use of mobx. What was it made for, then? )))

You are returning the size of the first item. The size object itself hasn't actually changed, its still the same object (the size of the first item). Its properties however have changed, but you are not reacting to those, only to the object.

Autorun prevents these reasoning puzzles quite nicely and its the best way to avoid having to solve them.

I removed toJS() and my code worked!

https://codesandbox.io/s/thirsty-neumann-7pw1y

image

Keep in mind that if you do things like trigger some window resizing based on this reaction you will still have a bug, because it will only react to changes which is the first item, not on the item changing its internal size.

To fix the bug you actually need to add to toJS() in the first reaction function. That will force dependency on the first item size. But at that point you're basically using autorun so why not just do that?

What you are seeing in the console is the latest version of the object, as the console takes the object reference rather than the value. toJS gives you the real snapshot of the value. Please use autorun.

What was it made for, then? ))

Reaction does't run the effect immediately by default.
Reaction runs the effect only if returned value differs from the one returned previously.
Consider:

reaction(
  () => {
    // runs anytime the size or width or height changes 
    return this.size.width * this.size.height;
  },
  area => { 
    // runs only if area differs 
    console.log(area);
  }
);

autorun(() => { 
  // runs anytime the size or width or height changes
  const area = this.size.width * this.size.height;
  console.log(area); 
});

I don't think it's the problem of the toJS() function.
If you add this line console.log("-----size=", size.height);
, you will notice the height is still 0 not 1000, which means this reaction still can't catch the internal change of size object.

I realized that I do not understand how to use mobx!

He said and I am closing 😎

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs or questions.

Was this page helpful?
0 / 5 - 0 ratings