-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
[breaking change] Get rid of field initializers (and legacy decorators) #2288
Comments
Thank you for a nice heads up. |
Oh wait, I got that all wrong (kinda sleepy). This isn't about mobx building, but users running TS 3.8 will have this problem... Well that goes beyond my expertise why is it a problem. Basically this... class State {
constructor() {
this.value = 0;
}
} Becomes... class State {
constructor() {
Object.defineProperty(this, "value", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
}
} And MobX cannot handle that for some reason. |
Nope, I protested heavily when TC 39 proposed this, for reasons like this,
but to no avail.... Field initializers will be non-interceptible once this
finalizes, so the assignments will have to move to the constructor instead
…On Sat, Feb 15, 2020 at 10:07 PM Daniel K. ***@***.***> wrote:
Oh wait, I got that all wrong (kinda sleepy). This isn't about mobx
building, but users running TS 3.8 will have this problem... Well that goes
beyond my expertise why is it a problem.
Basically this...
class State {
constructor() {
this.value = 0;
}
}
Becomes...
class State {
constructor() {
Object.defineProperty(this, "value", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
}
}
And MobX cannot handle that for some reason.
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#2288?email_source=notifications&email_token=AAN4NBDQTWQWA7C3GKZV6MDRDBRR7A5CNFSM4KV4PIV2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEL3YB3I#issuecomment-586645741>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAN4NBAATFBXU4AXHN3MGV3RDBRR7ANCNFSM4KV4PIVQ>
.
|
So if I understand this correctly to support classes with this we will need to resort to something like: import { observable, extendObservable } from 'mobx';
class State {
constructor() {
extendObservable(this, {
value: this.value,
});
}
value = 0;
} or maybe import { observable, initializeObservables } from 'mobx';
class State {
constructor() {
initializeObservables(this);
}
@observable value = 0;
} That's really unfortunate 😭 |
Or ditch classes :) We just had a short conversation yesterday how decorators are annoying and probably never going to get finished. Sure, the problem remains with In I understand it's not the best solution for existing code, nobody is going to migrate from classes, but it's at least some path forward. We should probably add a big warning to README here for TS users so they can disable that config option. @xaviergonz What are your opinions here? I suppose that mobx-keystone is going to be affected by this too. |
Ditching classes is not really an option for us, we're using them extensively through our applications. They certainly have their shortcomings, but they also provide us with some limited reflection capabilities which come in handy. I think being forced to trigger initialization in constructor, but keeping import { observable, initializeObservables } from 'mobx';
class ObservableObject {
constructor() {
initializeObservables(this);
}
}
class State extends ObservableObject {
@observable value = 0;
} |
I did not mean ditch classes like remove complete support for them. Just to think of them more like legacy patterns and move forward with something with fewer shortcomings. Can you elaborate on what reflection capabilities they provide? I am not familiar with that. Your workaround definitely makes the most sense at this point. But why not to disable that TS option instead of modifying the code? |
I'm talking mostly about interface TodoApi {
create(todoCreate: TodoCreate): Promise<Todo | ValidationState>;
}
onCreateClick = async () => {
// you can recognize what was returned without having extra fields
// or wrapping the results
const result = await this.todoApi.create(this.form);
if (result instanceof ValidationState) {
this.errors = result;
return;
}
this.router.goTo(`todos/${result.id}`);
} |
Afaik class `{ @observable x; constructor () { this.x = 3 } }` will still
work.
Op zo 16 feb. 2020 12:32 schreef Lukáš Novotný <notifications@github.com>:
… I'm talking mostly about instanceof and being able to use discriminated
unions without hassle:
interface TodoApi {
create(todoCreate: TodoCreate): Promise<Todo | ValidationState>;
}
onCreateClick = async () => {
// you can recognize what was returned without having extra fields
// or wrapping the results
const result = await this.todoApi.create(this.form);
if (result instanceof ValidationState) {
this.errors = result;
return;
}
this.router.goTo(`todos/${result.id}`);
}
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#2288?email_source=notifications&email_token=AAN4NBHOGX3HOCLKVKMCHW3RDEW4XA5CNFSM4KV4PIV2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEL4FSZA#issuecomment-586701156>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAN4NBA23DXBJIFV2KIKPXTRDEW4XANCNFSM4KV4PIVQ>
.
|
I can think of a way to make it work automatically, but it requires "overriding" Object.defineProperty for certain scenarios: console.clear()
const propOverridesSymbol = Symbol()
const origDefineProperty = Object.defineProperty;
Object.defineProperty = function(o: any, p: string | number | symbol, attributes: PropertyDescriptor & ThisType<any>): any {
const overriden = o[propOverridesSymbol]?.has(p); // will get it from prototype if available
if (!overriden) {
return origDefineProperty(o, p, attributes);
} else {
// just set value
o[p] = attributes.value;
return o;
}
}
function markAsOverridenDefineProperty(o: any, p: string | number | symbol) {
if (!o[propOverridesSymbol]) {
// should be a hidden prop instead in a final implementation
o[propOverridesSymbol] = new Set();
}
o[propOverridesSymbol].add(p);
}
function deco(proto: any, key: any): any {
Object.defineProperty(proto, key, {
get: function() {
console.log("deco get")
return Reflect.get(this, key + "int");
},
set: function(value) {
console.log("deco set", value)
return Reflect.set(this, key+ "int", value);
}
});
markAsOverridenDefineProperty(proto, key);
}
class State {
@deco value = 0;
}
const classState = new State();
// prints deco set 0
classState.value = 10;
// prints deco set 10
class State2 extends State {
@deco value2 = 1
}
const classState2 = new State2() |
The only place where it wouldn't work is when using ESNEXT as target and
which won't use the modified Object.defineProperty but an internal one. Btw, this won't work:
since in that case the base constructor would get called before the final Object.defineProperty is called. To solve that case (and actually also the problem where the class properties are not transpiled) you could use, ironically, a decorator: @observableClass
// returns class X extends State {}
// with a constructor that calls the State constructor and then does the defineProperty of observable
// values
class State {
@observable value = 0;
} but I guess it is fair to use a decorator to solve a decorator problem? Or alternatively (as mentioned before) calling a function on the constructor: class State {
@observable value = 0;
constructor() {
initObservables(this); // would restore overridden defineProperties
}
} |
yeah, that is the kind of thinking I have now as well, the only way that
pops to my mind to fix it, is indeed by having a class level decorator :)
At least the migration path of that wouldn't be too awful, only some tricky
edge cases I guess around inheritance
…On Sun, Feb 16, 2020 at 3:38 PM Javier Gonzalez ***@***.***> wrote:
The only place where it wouldn't work is when using ESNEXT as target and
useDefineForClassFields, since that transpiles to
class State {
value = 0;
}
which won't use the modified Object.defineProperty but an internal one.
Btw, this won't work:
class ObservableObject {
constructor() {
initializeObservables(this);
}
}
class State extends ObservableObject {
@observable value = 0;
}
since in that case the base constructor would get called before the final
Object.defineProperty is called. To solve that case (and actually also the
problem where the class properties are not transpiled) you could use,
ironically, a decorator:
@observableClass// returns class X extends State {}// with a constructor that calls the State constructor and then does the defineProperty of observable// valuesclass State {
@observable value = 0;
}
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#2288?email_source=notifications&email_token=AAN4NBGGMIK2G642IR6J5X3RDFMXDA5CNFSM4KV4PIV2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEL4KIZA#issuecomment-586720356>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAN4NBBL2JTGZUYSCMAP3Z3RDFMXDANCNFSM4KV4PIVQ>
.
|
I was thinking about... @decorate({
value: observable,
})
class State {
value = 0
}
// no @
decorate({
value: observable,
})(class State {
value = 0
}) But personally I would most likely prefer to decorate/extend in constructor (not sure): class State {
value = 0
constructor() {
decorate(this, {
value: observable,
})
}
} EDIT: Or eventually plugin to babel, comment driven definitions? |
For reference, this is how Ember does it (since basically two month, I guess we have been some inspiration :)): class Person {
@tracked firstName;
@tracked lastName;
@tracked age;
@tracked country;
constructor({ firstName, lastName, age, country }) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this.country = country;
} |
@mweststrate Could you elaborate why your sample would work? The property is defined no matter whether it has default value or not according to spec. By the way - babel situation is similar: Not working at all (modern decorators, non-loose fields): Only decorators (legacy decorators, non-loose fields): Fully working (legacy decorators, loose fields): I think babel modern decorators could work though: Babel repl sample As far as I can tell:
I've spent several hours on researching this today and I feel like the only way forward is @urugator suggestion: class State {
value = 0;
constructor() {
decorate(this, {
value: observable
});
}
} |
The only thing I don't like about solutions inside the constructor (besides that the migration path is a bit harder) is that they force you to write constructors for extended classes. class A {
@observable x = 0
constructor(a, b, c, d, e) {...}
}
class B extends A {
@observable y = 0
} vs class A {
x = 0
constructor(a, b, c, d, e) {
decorate(this, {x: observable})
}
}
class B extends A {
y = 0
constructor(a,b,c,d,e) {
super(a,b,c,d,e)
decorate(this, {y: observable})
}
} but other than that I guess it is ok (and actually very cross platform!).
That'd need to be something like const State = decorate({
value: observable,
})(class {
value = 0
}) which doesn't get too well with generic classes and typings. e.g. const State = decorate({
value: observable,
})(class S<T> {
value: T = 0
})
type State = typeof State
const s: S<number> = new S<number>(...) // : S<number> won't work (PS: I still think doing a |
@Kukkimonsuta you are right, was still assuming some old behavior where I don't feel that decorators are moving anywhere some, so a change in the proposals (if ever some proposal does get at stage 3 again) might bite us, like field initializers do now, where the final spec deviates from all reference implementations. So ideally we'd find a solution that doesn't rely on decorators at all. (A nice benefit is that it would drop a lot of code from the lib!). So I think @urugator's proposal are the clearest way forward, where probably the in-constructor variation is the simplest one (constructor wrapping is very hard, and without it we would still need to rely on the ugly babel hack that initializes observables on first read). I think it should be possible to create code-mods that rewrites the decorators (would someone be interested in building one?). (For babel a babel-plugin-macros could work as well if just the syntax is enabled?) Still, I'm a bit sad that we will probably worse the DX as a result of the language progressing.... But alas, at least it will work out of the box with CRA. I hope to experiment a bit further in coming weeks to try and feel what works best TODO: after some initial poccing, open up a fresh issue to discuss into more detal |
useDefineForClassFields
breaks decorators
Related TypeScript issue here: microsoft/TypeScript#35081 Unless I missed something traps should be still doable when leveraging decorators (= just replacing descriptor, no custom
Considering TypeScript fields are the outlier we might be able to convince TypeScript team to properly address this. Code here: |
I'd love if TS fixed it on their end, but I think that to address it they'd need to enable some sort of transpliation when a field has a decorator and the target is set to "esnext". Right now when useDefineForClassFields is true and the target is set to esnext there's absolutely no transpliation involved whatsoever, but I see no way to make it work without transpilations since browsers now internally use defineProperty for class properties. In other words, they'd need to move the decorate call for fields to the constructor rather than work over the prototype. |
I've made a simple PoC babel plugin, transforming: class A {
// @whathever
field = value;
}
// into
class A {
// @whathever
field = value;
constructor() {
decorate(this, {
field: whatever,
})
}
} If there would be interest I think I could turn it into something usable. |
It sounds like an interesting approach for sure to remove the burden for the glue code from the developer and have it as an automatic tool. It might make more sense to do it under babel-macros, adding extra plugins to CRA is annoying. |
It would be more interesting if it would transform the class directly (without the need to call |
Can a babel plugin also be run as a code-mod? I think that would be the
most future proof? (as it also removes the dependency on decorator syntax
etc)
…On Wed, Feb 19, 2020 at 9:54 PM urugator ***@***.***> wrote:
It would be more interesting if it would trasform the class directly
(without the need to call decorate at runtime) and also support @action
async fn, @action constructor and @observer (in a way you that you
wouldn't need mobx-react).
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#2288?email_source=notifications&email_token=AAN4NBHWCNQHQIXOELIOQFDRDWTCNA5CNFSM4KV4PIV2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEMJ2UMA#issuecomment-588491312>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAN4NBFSKUH5JOWYJZQ2HIDRDWTCNANCNFSM4KV4PIVQ>
.
|
Things might get tricky if you want to support JS & TS projects. At the moment you have a project that's not compiled by Babel, but by TSC, the AST would be different (not sure how much). There is probably a reason why ts-codemod exists. @urugator I wonder how do you want to achieve reactive functional components with Babel plugins :) |
@spion sorry, I glanced over your original post too quickly and entirely misread it. Do you have an example on how the proxy version looks like? I think it suffers from the same typing issue with generics that @xaviergonz pointed out? |
IMHO for properties, defaulting to computed is fine (all my properties are computed). Functions can be either actions or not and erring in either direction is a problem. Maybe the default should be to require all functions to be mentioned explicitly. Additionally, there should a way to specify the behavior for all non-listed functions.
+100. From a beginner's POV, decorators are the only sane way. Anything else is either too error-prone or too cryptic. |
@Maaartinus how'd you feel about proposal 6 in that case? |
@mweststrate it looks the same as the non-proxy one, except there is a class decorator class MyModel {
constructor() { tc39sucksInConstructor(this); }
@obs v = 1;
@computed get x() {
return this.v + 100;
}
} vs @tc39sucks
class MyModel {
@obs v = 1;
@computed get x() {
return this.v + 100;
}
} I'm pretty sure there are other ways to do it too, the main idea is that property decorators only provide metadata on which the class constructor acts - we modify the class constructor either via the decorator + proxy, or manually (by calling |
That depends on how bad the listed "Cons" get in reality. Such problems can really take ages for a beginner and are in no way related to the real work. Documentation can help or confuse even more; for me, an example covering all known problems (sorted from simplest) would be best. I really hope, we can continue decorators, as anything else feels like a huge step backwards. I could imagine using a naming convention for differentiating between actions and plain functions. I guess, I'd go for it... naming conventions are good per se and once you get used to using them, they make the coding less error-prone. Sort of Hungarian notation in the dark ages when it made sense. |
@spion I'd be interested if there actually are, looks pretty solid to me. Theoretically the inheritance chain is one deeper, but practically I'm not sure that matters. Even statics seem to be inherited correctly. I think the approach you are proposing is very neat, as it provides both a way to be backward compatible for easy migration, and a way forward in which we easily can opt-out from decorators if needed.
I think this way we can fix the field initializer problem that will hit us soonish. We also decouple a bit from the actual decorator implementation, and there is a clear way to opt out from decorators by using either Since Creating a code mode that generates / updates a constructor should be doable as well (although passing the correct args to a |
@mweststrate Sounds like a show stopper for CRA based apps that cannot (officially) configure Babel plugins. Feels like extra hurdle people might be reluctant to accept. |
@FredyC for them it will remain the same; they can configure through Edit: I think babel macros are now supported, so that might be an escape to using decorators? not sure if macros can handle syntax extensions |
Do you really need decorator metadata to make it work? I'd guess the only thing needed to make it work is the class prototype and the field/method name, which you both get even without decorator metadata. The decorator could then do something like prototype[someSymbol].push({ // someSymbol is a hidden prop
propertyName,
decoratorInfo
}) While the class decorator would call the original constructor + then constructor init function and iterate over the prototype symbol info to apply the desired behavior over the instance |
Does this mean that the |
@xaviergonz yeah, it is more babel I am worried about :) iirc babel-decorators-legacy is not compatible with modern field initializers, but above mentioned plugin (hopefully) is. So my goal is to have a simple decorator implementation that is compatible with babel, TS and define semantics. But which combination actually works has to be investigated still. Just want to figure out the general direction first |
I only used Either way, I don't think the emitDecoratorMetadata mode is required. Using the reflect-metadata module is less invasive, but also optional - although it does take care of poly filling Map/Set as appropriate. |
@mweststrate Am I correct that your sixth example will not work because primitives should be wrapped in class Todo {
width = observable.box(20);
height = observable.box(10);
surface = computed(() => {
this.height.get() * this.width.get() * this.utility();
})
double = action(() => {
this.width.set(this.width.get() * 2);
})
utility() {
return 1;
}
} This example is much more verbose. Or there are plans to get rid of |
the idea is that `initializeobservables(this)` could automatically unbox
the observable boxes (observable objects already use an observable box per
property behind the scenes). So techincally it would be pretty straight
forward to have it behave the same: `width = observable(20)` creates an
observable.box, then initializeObservables generates a getter and setter
for width that redirect to the box's getter and setter.
…On Wed, Mar 4, 2020 at 12:21 PM Egor Gorbachev ***@***.***> wrote:
@mweststrate <https://github.com/mweststrate> Am I correct that your
sixth example will not work because primitives should be wrapped in
observable.box? #2288 (comment)
<#2288 (comment)>
This is how your example will look like with observable.box:
class Todo {
width = observable.box(20);
height = observable.box(10);
constructor() {
initializeObservables(this)
}
surface = computed(() => {
this.height.get() * this.width.get() * this.utility();
})
double = action(() => {
this.width.set(this.width.get() * 2);
})
utility() {
return 1;
}
}
Or there are plans to get rid of observable.box?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#2288?email_source=notifications&email_token=AAN4NBGWOADMCRQZFAVAG33RFZBUVA5CNFSM4KV4PIV2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOENXS4QY#issuecomment-594488899>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAN4NBC7ANMFVJ7KOLP4JQ3RFZBUVANCNFSM4KV4PIVQ>
.
|
So I am still limping on two different lines of thoughts here,
Edit: Some further realizations: option 2 doesn't require any transpilation at all in modern JS. option 1 shares methods by default on the prototype (pro!) but 2 binds them by default (a different pro) (non-bound shared methods is still possible, by doing e.g. Edit: hypothesis: initializers that refer each other need to make sure to read 'boxed'. (but at least that would result in tracked reads in contrast to when using decorators, which might be even more confusing. Anyway, lets make sure we cover that in tests in any case) Edit: created a poll because the collective twitter wisdom can always be trusted: https://twitter.com/mweststrate/status/1235227165072822272 |
class Counter {
@observable declare count: number;
constructor({ count }: Counter) {
this.count = count;
}
} The |
I dunno... do we create observable objects like this: const o = {
width: observable.box(20),
height: observable.box(10),
surface: computed(() => {
this.height * this.width * this.utility();
}),
double = action(() => {
this.width = this.width * 2;
}),
};
initializeObservables(o); How do I create an actual box? class {
width = box(box(20));
computed = computed(computed(() => {}));
constructor() {
initializeObservables(this);
}
} How do I create a ref to box? class {
width = ignoreMe(box(20));
constructor() {
initializeObservables(this);
}
} What about other "boxes": array/map/set (or other classes?) class {
map: observable.map(); // is this.map observable?
map: observable.box(observable.map()); // or do i need "extra" box
constructor() {
initializeObservables(this);
}
} If we want to simplify things I still vote for: // Creating object:
const o = observable(object, decorators)
// Extending object
o.newProp1 = "a";
extendObservable(object, decorators)
// Extending instance
class {
constructor() {
extendObservable(this, decorators)
}
}
// In all cases the default behavior is same:
// fn => action (bound)
// get => computed
// other => observable
|
Have to go atm, so quick reply without looking into individual cases, but
we'd always box, which would be basically no different from now where
`@observable x = {}`, just like `x = observable({})` would both create an
observable object, and an observable property that holds that object
…On Wed, Mar 4, 2020 at 6:25 PM urugator ***@***.***> wrote:
reduces the many ways of achieving the same thing in MobX
I dunno... do we create observable objects like this:
const o = {
width: observable.box(20),
height: observable.box(10),
surface: computed(() => {
this.height * this.width * this.utility();
}),
double = action(() => {
this.width = this.width * 2;
}),
};initializeObservables(o);
How do I create an actual box? Like this?
class {
width = box(box(20));
computed = computed(computed(() => {}));
constructor() {
initializeObservables(this);
}
}
How do I create a ref to box?
class {
width = ignoreMe(box(20));
constructor() {
initializeObservables(this);
}
}
What about other "boxes": array/map/set
class {
map: observable.map(); // is this.map observable?
map: observable.box(observable.map()); // or do i need "extra" box
constructor() {
initializeObservables(this);
}
}
------------------------------
If we want to simplify things I still vote for:
// Creating object:const o = observable(object, decorators)
// Extending objecto.newProp1 = "a";extendObservable(object, decorators)
// "Extending" classclass {
constructor() {
extendObservable(this, decorators)
}
}// In all cases the behavior is same:// fn => action// get => computed// other => observable
extendObservable can additionally look for decorators on prototype if one
prefers an actual @decorators or decorate
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#2288?email_source=notifications&email_token=AAN4NBBMRZND4QHYGGABKOTRF2MJ5A5CNFSM4KV4PIV2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOENZNNEQ#issuecomment-594728594>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAN4NBD4FGKHHHKE67L27ZLRF2MJ5ANCNFSM4KV4PIVQ>
.
|
What about using Babel macros? I have no idea whether the necessary transformations are possible in macros, but it is worth checking. |
@urugator care to share the babel transform?
No, we can keep that as is / support both. The main goal is to be able to convert classes while keeping 'decorators' co-located
I don't think there any actual use case for that, but if there is, an assignment in the constructor or double wrapping would work in deed
yes, like with @wickedev
It is an interesting thought, but I feel like we have been kept piling rules and ways to make exceptions to rules on top of each other (e.g. calling actions from reaction is quite valid, and lazy loading patterns where a computed might trick a side effect action if called for the first time, like in mobx-utils, or making props observable or stores mobx-react(-lite) observable has resulted in a lot of In hindsight I'd love to rely on naming convention, e.g. only I think in the end if we don't have enough info to always make the correct choice, opt-in is better than opt-out. That being said, feel free to flesh out a bit how that api would look like? Also, better name for |
Additionally: Therefore:
Sure, but keep in mind it's the first thing I tried with babel and it's very unfinished. Personally I don't need it for anything, just wanted to try something new: |
If the import customPolicy from './mobx-policy';
const customInitializeObservables = (self, options) => initializeObservables(self, customPolicy, options);
//...
customInitializeObservables(this,{...}) |
And maybe something like Using I'm unsure what's more common, actions or views. Maybe there should be For naming conventions,
As it's about the only user-facing thing mobx does, |
Closing this one now in favor of #2325, to avoid that the discussion happens in two places. |
@xaviergonz this seems to work (no inference but not terribly annoying) |
Enabling
useDefineForClassFields
in TypeScript will prevent decorators from working (both transpiled and usingmobx.decorate
).This flag will be enabled by default for
ESNext
once class fields go stage 4: microsoft/TypeScript#34787Possibly related to #1969
Intended outcome:
autorun
usingobjectState
is executed upon clicking on "increment" button.autorun
usingclassState
is executed upon clicking on "increment" button.autorun
usingclassStateNoDecorator
is executed upon clicking on "increment" button.Actual outcome:
✅
autorun
usingobjectState
is executed upon clicking on "increment" button.⛔️
autorun
usingclassState
is NOT executed upon clicking on "increment" button.⛔️
autorun
usingclassStateNoDecorator
is NOT executed upon clicking on "increment" button.How to reproduce the issue:
https://codesandbox.io/s/fragrant-frog-x2487
Versions
TypeScript 3.7+
MobX - all tested versions (4.x, 5.x)
The text was updated successfully, but these errors were encountered: