nx-js / observer-util

Transparent reactivity with 100% language coverage. Made with ❤️ and ES6 Proxies.
MIT License
1.2k stars 94 forks source link

Deeply observe object #32

Open naturalethic opened 6 years ago

naturalethic commented 6 years ago

I would like to be able to observe for any change in a deep object.

minicuper commented 6 years ago

you can

naturalethic commented 6 years ago

Source:

const { observable, observe } = require("@nx-js/observer-util")

const o = observable({
  foo: {
    bar: {
      baz: 1
    }
  }
})

const ob = observe(() => {
  console.log(o)
})

o.foo.bar.baz = 2
o.foo.bar.baz = 3

Complete output:

{ foo: { bar: { baz: 1 } } }
CheloXL commented 6 years ago

Proxies are created on demand, so the above code would never work, since no proxies are created for foo, bar and baz. To do so, the library should have to walk the entirely tree and create a proxy for every object on it on registering the observable.

If you need that functionality, you can implement that yourself by doing exactly that: recursively walk the tree and observing every property.

naturalethic commented 6 years ago

Ok, just for background this was in the context of react-easy-state, using a third party widget that takes a tree of data, i needed the widget to update whenever any element of the tree updates. I ended up using a different means for this case. It does seem like observing an entire tree is out of scope for react-easy-state.

Thanks.

solkimicreb commented 6 years ago

Hey, this should work, I will check it in a sec.

solkimicreb commented 6 years ago

Sooo, this lib (and easy state) intercepts everything that 'can be intercepted'. Built-in functions in JS are sometimes implemented in none JS code (C++ for example). The above console.log statement is one of these cases, it recursively fetches the object keys in none JS code, which Proxies can not intercept. Consequently, the lib does not register the set of observable keys.

You can try this code for example and it will work.

const { observable, observe } = require("@nx-js/observer-util")

const o = observable({
  foo: {
    bar: {
      baz: 1
    }
  }
})

const ob = observe(() => {
  console.log(JSON.stringify(o))
})

o.foo.bar.baz = 2
o.foo.bar.baz = 3

JSON.stringify is accessing the keys in JS, so the lib can intercept the observable get operations. Sadly I can not give any guarantees about the behavior of built-in functions, their behavior with Proxies is not yet specified by the spec. Last time I checked observe(console.log) was working in Node.

Anyway, people rarely want to observe built-in functions, they usually observe 'normal JS code', which works all the time.

I hope this helps. (Could you include a small repro of your easy-state issue?)

solkimicreb commented 6 years ago

Your problem with easy-state was probably something very different.

I see you were passing a tree of data to a third party component and expected it to re-render when the data tree was mutated. In this case the issue was that the third part component was not a view (observer). It did not know that it should re-render when the data changes.

Your component was not using the data (it was not accessing parts of it), it just passed it on to the third party component. That component used the data, but it was not a view.

This seems the be a pretty common use case, I will try to think about a good alternative for it by tomorrow.

matthew-dean commented 5 years ago

Wait, nested objects aren't made observable? Why not?

matthew-dean commented 5 years ago

From react-easy-state docs: Use nested properties, computed properties with getters/setters, dynamic properties, arrays, ES6 collections and prototypal inheritance

solkimicreb commented 5 years ago

@matthew-dean

Wait, nested objects aren't made observable? Why not?

How did you come to this conclusion? Nested objects are made observable on demand.

manico commented 5 years ago

Is there an example how to observe every property in nested object? It seems that you have to explicitly use proxy observable inside observe function (value cannot be passed to it).

3cp commented 3 years ago

BTW, some examples in this repo's README is wrong.

observe(() => console.log(person), { scheduler: ... });

That log only observes person itself, not nested properties. Should be:

observe(() => console.log(JSON.stringify(person)), { scheduler: ... });

Or some kind of explicit access of person.name and person.age.