Hello I have 2 doubts on what to observe and how to modify observables.
1) I have a store
const store = {
@observable myProp1 = obj1;
@action setProp1 = (value) => {
this.myProp1 = value;
};
store.myProp1 is an object, so all it properties will became also observable.
Some of my observers observe (de-reference) the whole store.myProp1 object and other observe one of its props, like store.myProp1.innerThingy.
When observing the whole store.myProp1 I guess the observers will react only when assigning a completely different object to myProp1, and so changing any of its properties wouldn't trigger a reaction in this case, right?
Q: am I allowed (or should I) dereference the whole object or only its inner properties?
Which one is the best practise here?
2) Related to 1). When I want to update myProp1, the setProp1 action is actually re-assigning another object to store.myProp1.
Q: Is this the correct way to do this? Or this is inefficient because it needs to re-create an observable and also lose the old references? Should I only update the obj1 inner properties? meaning Object.assign(this.myProp1, {innerThingy: 'a new value'}
Which one is the correct (or best) way to do this?.
Q: am I allowed (or should I) dereference the whole object or only its inner properties?
I think you shouldn't need to think about this. Use data which are needed by the reaction - it could be myProp or innerThingy based on what you really need to access. There is only one limitiation and that is the value itself can't be observable, so you don't want to work with the values alone, but rather with observable objects until it's really unavoidable (until you encounter API which accepts values alone).
Q: Is this the correct way to do this?
You always want to modify so called minimal state.
However the granurality of the minimal state is up to you - is part of this minimal state the myProp1 or myProp1.innerThing? In other words...
Can innerThing change independently of myProp and is something interested about the change of myProp.innerThing without being interested about the change of myProp at the same time?
In the end, it's a matter of optimization - for the most optimal performance, you want to modify the smallest "units/atoms/observables/whatever", because you can react to their change alone.
For the lowest performance, but smallest application complexity you want to keep everything stateless (replacing immutable objects as a whole).
Thanks!
There is only one limitiation and that is the value itself can't be observable, so you don't want to work with the values alone, but rather with observable objects until it's really unavoidable (until you encounter API which accepts values alone).
Sorry I don't understand this part 馃 could you please elaborate this concept?
About the second question, when I've already declared an observable:
@observable myProp1 = obj1;
what really happens when I reassign the reference to another object
this.myProp1 = obj2;
Is another observable created? Or the observable part is myProp1 that now poitning to another object consider itself changed and trigger a reaction somewhere where store.myProp1 is used?
I guess this is related to the doubt about the quoted text above.
For the lowest performance, but smallest application complexity you want to keep everything stateless (replacing immutable objects as a whole).
Meaning this?
this.myProp1 = obj2;
And why would this be less performant? As asked above maybe because a new observer needs to be created each time I reassign the reference?
Sorry I don't understand this part :thinking: could you please elaborate this concept?
I think it's clear, because it's pretty trivial, however quite important when designing API of your components/classes/functions.
const text = "text"; // values are not observable
const component = observer({ text } => {
// this component and all of it's descendants can't benefit from MobX in any way, because it's API accepts plain (non-observables) value
return React.createElement("span", null, text);
})
// Better
const text = observable("text"); // objects are observable
const component = observer({ text } => {
// this component and all of it's descendant can benefit from MobX, because it's API accepts observable object
return React.createElement("span", null, text.get()); // notice the value itself is used when it's really unavoidable
})
this.myProp1 = obj2;
Two things happen:
1) Because @observable (and not @observable.ref) was used, the Mobx will automatically attempt to convert the obj2 to observable object. So if obj2 is plain object, MobX will create a deep observable copy (further modifications of original obj2 won't affect the assigned observable copy).
2) The setter generated by MobX is invoked. The original value of this prop is completely lost and the new value (that deep observable copy) is assigned. Additionally, this setter notifies any observers(reactions) which dereferenced .myProp, it WON'T notify anything else.
And why would this be less performant?
The performance of the assigment itself is not important. The possible (not neccassary) problem is that it is the smallest detectable change. Consider something like:
@observable app = new App(state);
// if the state is changed we will simply recreate and replace the app
this.app = new App(updatedState); // analogy to this.myProp1 = obj2;
// automatically re-render when app changes
autorun(() => {
this.app.render();
})
Any single change (no matter how small) will lead to re-rendering the whole app (which may or may not be a problem), because the "change of app" is the smallest detectable change - for example you can't re-render just the left panel if only the left panel was changed.
@urugator thank you very much!
Let me recap my understanding for future reference:
@observer.refPlease correct any mistake or unclear points 馃憤
It's always better to use observable object.
By observable object I meant anything observable in general, including boxed values. The boxed value is also an object. The point is you can't have an observable value.
When this is not possible use boxed values instead
No, because you can always use object instead of boxed value. Use whatever suits you as long as it's observable:
const boxed = Mobx.observable("value");
const object = Mobx.observable({ value: "value" });
autorun(() => {
boxed.get();
object.value; // just different way to access the value
})
If I should be even more precise with terminology, there is nothing like observable object, because the object itself is a value:
let object = Mobx.observable({ key1: "val1" });
// let's change the object
object = Mobx.observable({ key2: "val2" });
// how do you detect this change?
// its not possible because object is value, it's not observable
So when refering to observables we actually mean either boxed value or an object with observable property (or computed which exists in both forms - as prop or standalone boxed).
To make a value observable you need some accessors .get()/.set()/.propName, because the accessors perform the subscription (during get) and notification (during set).
Performance does not depend of new observable assignment.
Well, obviously there is some overhead caused by cloning and making the object observable, but it's not the primary thing to take into consideration when deciding what should be modified or made observable.
If your observable are immutable then better to use @observer.ref
If you want to keep it immutable.
@LeonardoGentile did you check: https://mobx.js.org/best/react.html? It quite elaborates how observables are tracked and I think it should answer most of your questions.
@mweststrate yes, with that and the explanation from @urugator things are a bit more clear now. Thanks 馃憤
Cool! Could you formulate any remaining questions or close the issue :) tnx!
Most helpful comment
I think it's clear, because it's pretty trivial, however quite important when designing API of your components/classes/functions.
Two things happen:
1) Because
@observable(and not@observable.ref) was used, the Mobx will automatically attempt to convert theobj2to observable object. So ifobj2is plain object, MobX will create a deep observable copy (further modifications of originalobj2won't affect the assigned observable copy).2) The setter generated by MobX is invoked. The original value of this prop is completely lost and the new value (that deep observable copy) is assigned. Additionally, this setter notifies any observers(reactions) which dereferenced
.myProp, it WON'T notify anything else.The performance of the assigment itself is not important. The possible (not neccassary) problem is that it is the smallest detectable change. Consider something like:
Any single change (no matter how small) will lead to re-rendering the whole app (which may or may not be a problem), because the "change of app" is the smallest detectable change - for example you can't re-render just the left panel if only the left panel was changed.