class FilterCriteria {
@observable filter = new Map();
}
let criteria = new FilterCriteria ();
// setting up a reaction when something in the filter changes
// (property added, removed, or changed)
reaction(()=>criteria.filter, data => console.log(data.toJSON()));
criteria.filter.set('name', 'John'); // setting a new property.
I would expect the above code to print out { 'name': 'John' }
, but it seems that the reaction is not running.
I suspect that I set up the reaction in the wrong way. I want to react whenever a new key is added, an existing key is removed or a key value is changed. I don't know the keys or values at compile time.
How am I supposed to do that?
UPDATE
I changed my code to
class FilterCriteria {
@observable filter = new Map();
@computed get json(){ return this.filter.toJSON(); }
}
...
reaction(()=>criteria.json, data => console.log(data));
and now it seems to work properly. The reaction sideffect is executed whenever I add, remove or change a value in the Map.
So the question is why the reaction did execute in the second but not in the first example?
UPDATE 2
I changed my code again for a second time. I reverted to almost the first version but this time instead of reacting on criteria.filter
and logging data.toJSON()
, i react on criteria.filter.toJSON()
and I log data
(toJSON is moved from the sideffect to the value being watched). This time the reaction runs normally.
class FilterCriteria {
@observable filter = new Map();
}
reaction(()=>criteria.filter.toJSON(), data => console.log(data));
Again, I don't understand why. If criteria.filter
is not an observable in itself then how does the watched expression is reevaluated when something inside criteria.filter is changed?
UPDATE 4 (hope the final one) SOLUTION
According to MobX documentation, mobx reacts to any existing observable property that is read during the execution of a tracked function.
reaction side-effect executes when the observable property changes. In my example, when reacting to criteria.filter
, the observable property that is read here is filter
, but the filter itself never changes. It is the same map always. It is the properties of filter that change. So the reaction is never run for criteria.filter
.
But when I react on criteria.filter.toJSON()
or mobx.toJS(criteria.filter)
, the reaction is executed correctly.
So why is that? criteria.filter
doesn't change, and toJSON
is not an observable property. It is a function. same for mobx.toJS
. It seems no properties are read here. But this is not correct. As the documentation states (but not so emphatically), the properties of criteria.filter are indeed read when toJSON or mobx.toJS is executed, because both functions create a deep clone of the map (thus iterating over every property).
Now, in the beginning, the Map did not contain any property. So how is it that newly added properties are tracked, since they did not exist (to be read) when tracking begun? This is a map's feature. Maps provide observability for not yet existing properties too.
In MobX 5 you can track not existing properties of observable objects (not class instances) too, provided that they were instatiated with observable or observable.object. Class instances don't support this.