Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Watch for nested object/array changes #1207

Closed
hoang17 opened this issue Oct 18, 2017 · 9 comments
Closed

Watch for nested object/array changes #1207

hoang17 opened this issue Oct 18, 2017 · 9 comments

Comments

@hoang17
Copy link

hoang17 commented Oct 18, 2017

I have this nested object:

const pages = [
  {
    "id": 1,
    "childs": [
      {
        "id": 11,
        "childs": [
          {
            "id": 111,
            "childs": [..]
          },
         ...
        ]
      }
    ]
  },
  {
    "id": 2,
    "childs": [
      {
        "id": 21,
        "childs": [
          {
            "id": 211,
            "childs": [..]
          },
         ...
        ]
      }
    ]
  }
]

So each page will have multiple deeply nested childs. In Vue i can just use this code to watch for any changes in the tree:

watch(() => pages, (newval, oldval) => {
    console.log(newval)
}, {deep: true})

How can i do that in mobx?

Thanks

@urugator
Copy link
Collaborator

urugator commented Oct 18, 2017

If you need diffs, you have to manually traverse the tree and use observe. Note you also need to dispose all created observers at some point. You may also take a look at mobx-state-tree

If you only need to react to new values, then make pages observable
const observablePages = observable(pages)
and access the requested value in reaction/autorun/observer:

autorun(() => {
  console.log(observablePages[1].childs[0].id);
  // re-runs when observablePages array is modified or when childs prop is modified or childs array is modified or when id prop is modified
  // for more info see https://mobx.js.org/best/react.html
})

@leaysgur
Copy link
Contributor

leaysgur commented Oct 18, 2017

If you want to keep Vue-ish styles of code, you're also able to do it like this.

const { observable, reaction, toJS } = mobx;

const pages = [
  // your nested object/array goes here
];

// make it deep observable
const oPages = observable(pages);

// watch all changes
reaction(
  () => toJS(oPages),
  newVal => { console.log(newVal); },
)

oPages[1].childs[0].childs.push({ id: 212, childs: [] }); // logged newVal

But I don't recommend this because this is not good for performance...

@urugator
Copy link
Collaborator

@leader22 instead of JSON.stringify/parse use Mobx.toJS(oPages)

@leaysgur
Copy link
Contributor

@urugator Oh, exactly! I've just edit my comments. Thx! 😸

@hoang17
Copy link
Author

hoang17 commented Oct 18, 2017

@leader22 @urugator
Thanks for your quick replies! I just need to react to changes made in the tree (eg: save the tree snapshot). I think this code:

autorun(() => {
  console.log(observablePages[1].childs[0].id);
})

will not watch for all changes in the tree

@leader22 This is exactly what i need but as you said it is not good for performance if the tree is big with lot of deeply nested children

So I guess currently mobx has no proper way to watch for deep changes in a tree object like Vue

Could it be implemented in the future version of mobx?

@urugator
Copy link
Collaborator

I am not familiar with vue.js, but actually I think that performance wise:

autorun(() => {
  console.log(Mobx.toJS(pages));
})

could be pretty close to

watch(() => pages, (newval, oldval) => {
    console.log(newval)
}, {deep: true})

Why? If I understand right the subscription in vue (() => pages) is also dynamic. So the subscription function re-runs everytime the pages changes and due to the deep: true the pages object is traversed (so the added things can be tracked as well).
Which is basically what happens in MobX case, only the traversal (deep subscription) is done explicitely by calling Mobx.toJS.

@hoang17
Copy link
Author

hoang17 commented Oct 18, 2017

This is Vue watch api for reference https://vuejs.org/v2/api/#vm-watch
So if it is really the same then i think we should add deep option to mobx like vue api

@urugator
Copy link
Collaborator

urugator commented Oct 18, 2017

Aside observe (which is a different topic) there is no API which would accept a "specific thing to watch". So there is no place where deep option could be added or make sense.
In MobX the dependencies (things which need to be tracked) are resolved based on how they're being used.
It may seem somehow limiting to you now, but most of the time it makes a lot more sense than explicit subscription. You just have to start thinking a bit differently about it:
Instead of detecting the change, focus on what you want to do with pages object first.
Lets say you want to print the whole object, to do that you have to actually traverse the whole object to print it's props.
Or lets say you want to save the whole oject to DB, you will have to serialize the object first, which again means traversing.
Or if you want to create a snapshot, you have to create a deep copy of the object, which again means visiting each of it's props.
And now consider you want to do that only with a part of the object (and perhpaps conditionally). All you have to do is to perform these operations inside reaction/autorun/computed and MobX dynamically (un)subscribes only for the relevant parts.
The logic here is simple - if something isn't used (accessed) by the operation, it can't affect the output of the operation, therefore it doesn't make sense to watch for unused things.
So in reality you don't need to define which object to watch or how to watch it, the subscription happens naturally.

@hoang17
Copy link
Author

hoang17 commented Oct 19, 2017

@urugator make sense to me now. Thanks a lot for your explanation

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants