nx-js / observer-util

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

limitation on mutating observables in reaction #25

Open janat08 opened 6 years ago

janat08 commented 6 years ago

Is there a reason for why this limitation exists? I can see how this may cause loops, but otherwise there's nothing wrong with having reaction triggered in reaction (not in, after).

solkimicreb commented 6 years ago

Hi!

Mutating observables inside reactions won't trigger other reactions, but they don't break the app in any other way. Triggering other reactions in reactions could cause infinite loops and a very obfuscated control flow. Reactions are meant to react on data changes and not to trigger other data changes. (They should save or visualize data as examples).

In the past the lib allowed triggering reactions inside reactions and automatically cut the infinite loops, but I had to remove this when I added custom schedulers. With custom async schedulers and observable mutations inside reactions it would be possible to cause async infinite loops, which is a really nasty thing.

Do you have an example use case where you would like to mutate reactive data inside reactions?

PS: I also saw the other issue, where you referenced this. I would advise you against using NX framework in serious projects. Some parts of it (like this lib and the queue-util) are actively maintained and prod ready, but the project as a whole is stuck in beta until I have more time to finish it.

janat08 commented 6 years ago

Mobx has autorun, and meteor has autorun(it being the only thing they have). Mobx says it's meant as a bridge to imperative code, and I suppose it's a consumer of a reaction/computed prop, so I say reactive programming that isn't responsive to itself is seriously gimped.

solkimicreb commented 6 years ago

Mobx autorun is the equivalent of nx-observe observe, they are both meant to react on data changes not to update data (just log or persist it).

Mobx:

This is usually the case when you need to bridge from reactive to imperative code, for example for logging, persistence, or UI-updating code.

nx-observe:

Reactions are meant to react on data changes and not to trigger other data changes. (They should save or visualize data as examples).

MobX autorun also throws (or warns) if you mutate observables inside it. Overdoing transparent reactivity is a hell. You will quickly loose which function triggers which ones. This is why you can't call setState in React renders or can't mutate observables in MobX or nx-observe reactions.

Do you have a use case (code snippet) where you would like to update reactive data in reactions?

EDIT: the MobX docs is a bit poorly worded here. autorun is a bridge from imperative to reactive programming, not the other way around. computed is a bridge inside reactive programming, the nx-observe equivalent is vanilla ES5 getters. Perhaps this is what you are looking for, check the computed props example in this docs section.

janat08 commented 6 years ago

Might actually work.

janat08 commented 6 years ago

Anyway it feels a little weird to me. I'll give it a try, but here's meteor's guide to tracker: https://github.com/meteor/docs/blob/version-NEXT/long-form/tracker-manual.md, obviously it's somewhat slower.

janat08 commented 6 years ago

I'm trying to map observer api onto tracker api.

janat08 commented 6 years ago

Im looking to pass invariable amount of functions into getter for Dependency since it supports multiple calls on changed method, and actually be able to tell what function called the method in production.

solkimicreb commented 6 years ago

I feel like I can't help here, what's is your overall goal? Would you like to track which function uses which observable values?

In that case I suggest you to check the experimental debugger option. It is documented here and the simplest use looks like this:

observe(fn, {
  debugger: console.log
})

debugger expects a function, which receives a context object about the triggering mutations and reactive wiring. Passing console.log is the simplest way to use it, but you can write your own aggregating debugger if needed.

I don't know if this is what you are looking for.

janat08 commented 6 years ago

Convert meteor's reactive lib tracker to yours, im trying to do.

janat08 commented 6 years ago

Ur typical implementation for observable would be call new Dependency(), and then dependency.changed() when it was change in function, and dependency.depend() when the observable was used. I was thinking I should change dependency.changed( ) whould set observable on function where it was used as getter for dependency, but Im not sure that will work.