mobx: automatic tracking not working for nested objects?

I took the sample code from the documentation here and modified it a bit to use an object instead of an array for the todos field:

var todoStore = observable({
    todos: { },
    identity: function() { return this.todos; },
    derived: function() {
        let counter = 0; for(let key in this.todos) { ++counter; } return counter;
    }
});

const print = function() {
    console.log(
        JSON.stringify(todoStore.todos),
        JSON.stringify(todoStore.identity),
        todoStore.derived
    );
};

autorun(print);
// -> prints: { } { } 0

todoStore.todos.test = { title: "Take a walk", completed: false };
// -> should print: {"test":{"title":"Take a walk","completed":false}}
//      {"test":{"title":"Take a walk","completed":false}} 1

todoStore.todos.test.completed = true;
// -> should print: {"test":{"title":"Take a walk","completed":true}}
//      {"test":{"title":"Take a walk","completed":true}} 1

Now the whole automatic tracking doesn’t work anymore. The places marked with -> should print: ... do not print.

It I add another print(); call at the end of the sample code it prints this:

{"test":{"title":"Take a walk","completed":true}}
     {"test":{"title":"Take a walk","completed":true}} 0

Note the wrong 0 at the end, since this should be 1!

Do I miss something here? Do I need to use mobservable for objects differently from how I would use it intuitively?

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Comments: 15 (7 by maintainers)

Commits related to this issue

Most upvoted comments

Objects themselves are not observable. Only their individual properties are. When mobservable makes an object observable, it will only do so for the properties that are on the object on that moment. (and extendObservable can be used to add after-the-fact properties).

However, the set of keys, or the iterator, of an object is never observable (es5 simply hasn’t have a feature for that). So if you have the following:

var obj = observable({ x: 1 });

autorun(() => {
  for(var key in obj)
     console.log(obj.x)
})

obj.x = 2; // will be picked up
obj.x = { y: 3 } // will be picked up, obj.x.y will be observable

obj.y = 3; // creates non observable property y, will not be picked up, although it will affect the iteration of autorun
obj.x = 5; // now y will be printed as well, but future changes to y will not be picked up, it isn't observable

extendObservable(obj, { z: 4 }) // won't be picked up; although the property is observable, the for loop won't detect the addition as the key set of an object isn't observable
obj.x = 6; // this causes a rerun of the autorun now z will be detected by the autorun.
obj.z = 5; // will be picked up; in the last iteration the autorun subscribed to obj.z as well

So it boils down to this: anything with a fixed set of properties (plain data object, classes, etc. etc) can be made observable by using observable / extendObservable.

If you want to vary on the keys that exist on an object itself, you need map if the keys are non-numeric, and arrays if the keys are numeric. With map you can even subscribe to not yet existing keys.

So in your case you need a map as you index your objects by some key. The objects inside that map are probably regular so observable(obj) will suffice for them (observable will be applied to any new values added to the map automatically by default)

@Shadowman4205 try this one:

extendObservable(target, { [this.state.inputText] : ''} )