@faceyspacey, @mweststrate this is my opinion and experience trying to make Tracker sync via ReactiveState modifiers added to the A neat diagram comparing Meteor, Flux, and Relay post.
I’d love your comments about this stuff, regarding a possible Tracker 2.0 implementation.
I have not read the @mweststrate article about the minimum amount of computed values yet but I will do as soon as I can and I will post my thoughts here as well.
This is the post:
Mobservable is a great package. It’s transparent reactive programming (yeah, Tracker) for the React world and it shows the advantages of TRP in terms of code which is simple to reason about and less prone to edge case bugs and even performance gains when used right (check this https://www.mendix.com/tech-blog/making-react-reactive-pursuit-high-performing-easily-maintainable-react-apps/ out).
The main differences I see between Mobservable and Tracker are two:
-
Mobservable has all state inside and Tracker has state outside.
-
Mobservable is sync and Tracker is async.
Both are related. Sync stuff is easier to debug than async stuff.
I tried to make my ReactiveState package sync because when you own all state, you can execute stuff synchronously:
State.modify('tasks.areEmpty', (state = true) => {
if (State.get('tasks.items').length === 0)
return true;
else
return false;
});
State.modify('tasks.items', (state = []) => {
return Tasks.find().fetch();
});
When you are evaluating tasks.areEmpty you can stop at the State.get('tasks.items') and execute its modify first, make sure it is up-to-date and then continue with tasks.areEmpty. Functions are executed synchronously and only once. Perfect!
The problem with that is, once again: Meteor reactivity comes from many places, not only a single tree. For example, add another reactive source to the mix: Meteor.userId().
State.modify('user.hasPendingTasks', (state = false) => {
if (Meteor.userId() && State.get('user.tasks').length === 0)
return true;
else
return false;
});
State.modify('user.tasks', (state = []) => {
let userId = Meteor.userId();
return Tasks.find({ userId }).fetch();
});
If you want to make this work synchronously, you need to know if Meteor.userId() has already changed or is going to change before executing any of the State modifiers.
The sad truth is: you cannot know. Tracker reactivity has no priorities and is spread in many places along Meteor. So the solution is what Meteor people already did: make Tracker async and wait for every reactive source to invalidate its computations. Then start executing computations.
For that reason, I don’t think Mobservable is useful or has any advantage over Tracker in Meteor if you don’t want to throw a lot of stuff which is already working with Tracker.
For Tracker, this means sometimes functions are going to be executed twice or even more times until they reach an stable state. That’s the reason MeteorFlux actions have three phases:
- Register callbacks (sync non-reactive).
- State.modifies or Tracker.autoruns (async reactive, it keeps executing functions until everything is stable).
- AfterAction callbacks (sync after all reactivity has finished and is stable, non-reactive).
Explained like this, this looks like really complex stuff, but in the end coding with TRP is simpler and less prone to bugs. Most of the time you don’t have to know about how it works internally and that’s what makes Tracker and Meteor so simple to start with.