Managed to reproduce the dreaded message again:
Error: Reaction doesn't converge to a stable state. Probably there is a cycle in the reactive function: Reaction[update1]
I have made a semi-realistic AngularjS 1 + MobX 2 application at
https://jsfiddle.net/x8mnncd3/4/
It works. It reproduces, more or less, in a simplified way, the behavior of a form (in a dialog) that I have in our application.
In the update1 function in the autorun, I compute internal data out of a model, and view data out of internal one. Nice, cascading updates...
To test, just click on the Update button, it "simulates" getting data from a server, updating the model. Other controls (in view) are dependent on the model, if I click on one enabled radio-button, it changes another model object which triggers an update of the internal data.
All this works fine!
In the next iteration:
https://jsfiddle.net/x8mnncd3/5/
I tried to add internal data to the view, along with model data:
ctrl.view.message = 'Allowed: ' + ctrl._model.allowed.length + ' / Names: ' + ctrl._internal.nameList.length;
Ouch! I get the above error!
It is not intuitive (ie. I don't understand at all why I get this error!) because the data flows (apparently) unidirectionally from model to internal to view. I intently don't make the view reactive, hoping to avoid such cycle...
Apparently, that's the dependency of view on internal which is fatal.
Likewise, I tried to make a data structure to depend on internal data with a transformer (to avoid expensive updates), and I get this cycle.
So, I am doing something wrong? (Probably!)
Is my code bad? Breaking best practices? Odd? Unlogical? :smile:
How would you refactor it to avoid this issue?
Maybe I should make smaller observable objects?
Maybe I should use cascading transformers? (Haven't figured out how to do that in my case, yet.)
Maybe you have a bug somewhere? (I doubt it, that's why I appreciate to create an "issue", not a bug report... :stuck_out_tongue:)
If not, it might be worth documenting the problem somewhere... (I can try and do it, once I understood what is going on.)
Related: an explanation how autorun does its "magic" would be welcome... :smile: I suppose it relies on calls of getters / setters on observable objects during the assignments, but in this case it wouldn't know it has to observe objects in conditional branches. Mmm...
The last question is answered quickly: https://medium.com/@mweststrate/becoming-fully-reactive-an-in-depth-explanation-of-mobservable-55995262a254 On the rest I'll come back later :)
I have to re-read it more attentively, then... Fair enough! Thanks.
Here is an example on how to get cyclic dependency in MobX:
var _internal = mobx.observable({
noItems: 1
});
mobx.autorun(() => {
_internal.noItems = Math.random(); // we modify data that we depend on in autorun
// (and every time nextValue !== previousValue)
});
_internal.noItems++; // To start initial iteration
// We got cyclic dependency!
Above code is, basically, simplified version of what you have made:
mobx.autorun(function update1() {
ctrl._internal.noItems = _.isEmpty(ctrl._model.allowed); // We subscribe to _model.allowed so autorun
// is triggered when `Update button is clicked`
ctrl._internal.nameList = ctrl._transformers.nameList(ctrl._model.data); // we modify data that we depend on in autorun
ctrl.view.information = ctrl._transformers.information(ctrl._internal); // we use data we modify in autorun
// (ctrl._internal.nameList is used)
});
You don't need autorun here, really. autorun is good for DOING something: logging, displaying alert, setting checbox etc. Every time you use autorun for binding some values to another values its getting very hard to track those dependencies. I would suggest using computed values instead.
Made a quick refactoring just to give you idea of how it can look like: https://jsfiddle.net/Sl1v3r/r1gwhm10/
Thanks for taking time to refactor my code!
Little bug: when I click on Save, I get the message: "TypeError: Cannot set property message of #
Note: I used ES5 because that's what we use in our projects... But I understand you can prefer the ES6 syntax! Not a problem either.
nameList: () => ctrl._transformers.nameList(ctrl._model.data),
Aaah! From the documentation, I thought createTransformer could be used only in an autorun function! This is an eye opener...
Note: using Date.now() in a createTransformer was indeed a bad idea (wanting to make a more complex object), as the function is no longer pure. Bad hack, not in my original code, but something to watch for...
// We got cyclic dependency!
Oups! Indeed too easy to get them... As I was feeling, I abused of the autorun pattern (in my defense, the doc strongly advise to use it... :stuck_out_tongue:), and the trick above is probably what I was looking for.
I like less the checkIfDisabled calls in the choice object: they are called way too much, while it is a potentially costly operation (if ctrl._model.allowed is very big). That's why I prefer static assignments to view objects / properties. Perhaps a createTransformer can do the trick here.
I will play with my code to make it to evolve along your lead, and to remove my bad hacks...
Thanks again for your time!
Oups! Indeed too easy to get them... As I was feeling, I abused of the autorun pattern (in my defense, the doc strongly advise to use it... :stuck_out_tongue:), and the trick above is probably what I was looking for.
I'm sorry, I've given you wrong example, it's not the case in MobX 2.0, seems like we have some bug, researching.
edit, nope, example was ok, no bug :-)
I think this issue is solved now?
I know why I thought I should put all my createTransformer calls inside an autorun function:
Always use transformations inside a reaction like @observer or autorun.
From https://mobxjs.github.io/mobx/refguide/create-transformer.html
Is this information obsolete? Or is the usage in a mobx.obserable() call falling inside the "reaction" category?
To offer a performance gain it needs to be in a an autorun. But these can be indirectly, inside computed functions etc, as long as it is used somewhere inside an autorun. It will also work outside, but it won't do any memoization in that case.
createTransformer just creates a big pile of computations, a computation per value you pass into it. Computations always work as they can be evaluated always, but they are kept alive (memoized / cache) if there an at least one path from an autorun to the observable state.
Does that make sense? Otherwise I fear I have to draw an image :)
It is OK. As memoization seems to work in my case, I suppose it stems from the fact I consume the graph of dependencies in the autorun updating my view. So my pattern seems to make sense... :smile:
That said, images are always welcome... :laughing:
I wrote a small introductory text for my usage of MobX inside AngularJS applications. For my future self, and my co-workers that will have to maintain my code... :stuck_out_tongue:
I attempted to explain how MobX works, from educated guesses / my understanding of the mechanism / digestion of information here and in the doc / articles...
I have re-read again your article, and found out that indeed you describe the mechanism, just using "accessed" and "modified" where I wrote "getter" and "setter" (but of course it is beyond accessors, as splice, for example, is another modifier). It is just that I found that being a bit more specific helps in building the mental model, making it less "magic"... :8ball:
I will paste my text here (in another comment). It would be kind of you if you could read it and tell me if it is reasonably accurate? It is over-simplified, but I just want to be sure it isn't just misleading / incorrect. I point to your docs / articles for more accurate explanations.
MobX is a library simplifying state management by using functional reactive principles.
It manages dependencies between values, somehow like a spreadsheet does with its cells: if the value of a cell changes, other cells depending on the first one can change too, in a cascading way.
The manual of MobX can be read at MobX, Simple, scalable state management.
Note: I use the "controller as" pattern in my Angular application, and I assign this to the ctrl variable at the start of the controllers.
So I refer to ctrl.xyz for controller variables.
If you don't use this pattern, you can mentally replace ctrl with $scope.
Note: I tend to name functions that are traditionally anonymous, like iteratees of Lodash functions, etc.
Ie. instead of doing _.map(circles, function (c) { return TWO_PI * c.radius; ]; I write _.map(circles, function toPerimeter(c) { return TWO_PI * c.radius; ];.
This is generally a good practice, as it shows the intent of the transformation, and these names can show up in stack traces in debuggers, etc.
It is even more important with MobX, because it keeps tracks of these names and this allows an easier debugging of its mechanisms.
I have two objects on the controller, related to the view, ie. referenced in the template:
ctrl.viewModel contains the models, referenced by ng-model directives. Ie. they are changed by the user (text input, radio-buttons, check boxes, etc.).ctrl.view contains the view values, used in the template to display these values.Between them, I can have some intermediary objects, eg. ctrl._model for data structures, ctrl._internal for flags, etc.
These objects, and ctrl.viewModel, are observable by wrapping them in a mobx.observable call.
MobX transforms (recursively) each field (property) of these objects to observable values.
These values can be simple values set by the code (string, number, boolean); or arrays (each value is observed); or objects (each property is observed).
Observable values are replaced by a pair of accessors, ES5's getters and setters, or specialized objects, like ObservableArray, mimicking the behavior of real arrays, but observing each entry and operation (like splice).
If the property of an observable object is assigned a new value, MobX makes it observable too.
These values can be also be a function without parameters, called when the field is read.
The function acts as a getter, and can compute a value from other values.
If some of these values are observables, MobX invokes their getter, which in turn can invoke other getters, etc.
Instead of a direct computation from other values, the function can call a transformer, created via... mobx.createTransformer.
A transformer takes one (and only one) observable value as entry, must be a pure function (not using any closure except constants) and must return a new value computed from the parameter.
As we use properties from the object given as parameter, we invoke their getters. MobX keeps track of these sub-observable objects.
When a setter on their objects is called, MobX knows it has to recompute the result of the transformer.
Otherwise, it can return the previous value, said to be memoized. This results in a much faster response, particularly if the operation iterates on large collections.
The above is an over-simplified description how MobX works. The exact mechanism is described in the article Becoming fully reactive: an in-depth explanation of MobX. MobX has optimizations making all this working very fast...
ctrl.view is not an observable. It is already watched by Angular, and I prefer to keep it "static", assigning new values if their dependencies change.
For this, I use mobx.autorun which is used to do side-effects when an observable value inside it changes.
My side-effects here are to assign a new value to ctrl.view properties... I do that only for computation-intensive value evaluations (extracting data from collections, etc.), using transformers to memoize the results.
As createTransformer is a bit heavy-handed (needs some boilerplate code, accepts only one parameter), for lighter evaluations I use the ES5 getter syntax, function evaluated each time the property is read: var o = { get foo() { return someValue; } }; o.foo; // Calls the getter and return someValue
I formerly used mobx.computed for that, but I am not sure if it brings any advantage here...
Note: transformations must be used inside a reaction like @observer or autorun. They also work if they are part of the dependency graph pulled in an autorun, like done above (view depending on observable objects).
It might seem strange to use MobX with AngularJS, whereas the latter can $watch value changes.
MobX has two advantages here:
This avoids explicit $watch (I never use them in controllers anyway), evaluation of functions in templates (like ng-disabled="vm.isFooDisabled()") or manual calls to ctrl.updateState() which can be forgotten.
MobX allows to describe a graph of dependencies, and ensure it is always up to date.
For complex changes, like changing several values in an array, we can also use transactions, minimizing the recalculations.
MobX is a mature library, used in large applications, and it proved to be very efficient.
Spot on! Cool write-up :)
One minor correction:
A transformer takes one (and only one) observable value as entry, must be a pure function (not using any closure except constants) and must return a new value computed from the parameter.
You can use non constant values from the closure, you just have to make sure those are observable as well. If you do that the transformer will also re-run if this variable from closure changes.
N.B. This paragraph confused me a bit
As createTransformer is a bit heavy-handed (needs some boilerplate code, accepts only one parameter), for lighter evaluations I use the ES5 getter syntax, function evaluated each time the property is read: var o = { get foo() { return someValue; } }; o.foo; // Calls the getter and return someValue
I formerly used mobx.computed for that, but I am not sure if it brings any advantage here...
Not sure how to read it, but these things are all equivalent, it might clarify things:
function Task() {
extendObserable(this, { a: 2, myComputedProperty: function () { return this.a + 3 })
}
class Task {
@observable a = 2
@computed get myComputedProperty() { return this.a + 3 }
}
class Task {
_a = observable(2)
get a() { return this._a.get() }
set a(val) { this._a.set(val) }
_myComputedProperty = computed(() => this.a + 3)
get myComputedProperty() { return this._myComputedProperty.get() }
}
var task = observable({
a: 2,
myComputedProperty: function() { this.a } // dont use arrows here!
})
Thanks for the corrections. Good to know for transformers, I will fix that.
And will rework my other sentence.
Context: my view is not an observable. I used this:
ctrl.view =
{
saveDisabled: mobx.computed(function saveDisabled()
{
return ctrl._internal.noItems || !ctrl._internal.hasCommonStuff || ctrl._internal.isStarted;
}),
}
and switched to:
ctrl.view =
{
get saveDisabled()
{
return ctrl._internal.noItems || !ctrl._internal.hasCommonStuff || ctrl._internal.isStarted;
},
}
(ctrl._internal is observable.) I suppose computed has no advantage here?
Aah, unless perhaps it is among the things transformers must be in to be triggered?
Anyway, since ctrl._internal is used in an autorun in my code, it doesn't matter here.
But if it is a transformer trigger, it might be worth mentioning.
You can close the issue (or I will do), thanks for all the help and patience.
Btw, are you publishing the above story / experiences somewhere? Might be useful for others!
Yes, I plan to publish it on my blog: http://philhosoft.github.io/
Looking forward to it. Keep me posted!
Most helpful comment
Here is an example on how to get cyclic dependency in MobX:
Above code is, basically, simplified version of what you have made:
You don't need
autorunhere, really.autorunis good for DOING something: logging, displaying alert, setting checbox etc. Every time you useautorunfor binding some values to another values its getting very hard to track those dependencies. I would suggest using computed values instead.Made a quick refactoring just to give you idea of how it can look like: https://jsfiddle.net/Sl1v3r/r1gwhm10/