-
Notifications
You must be signed in to change notification settings - Fork 641
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
onBecomeUnobserved is never called #2211
Comments
Also, I used this issue as an inspiration for how onBecomeObserved and onBecomeUnobserved are used: #987 |
If I had to take a guess at why this is: there's a reaction against the snapshot of a tree node, so every property is always observed. If that's the case, I think it would be incredibly difficult for us to make this work without a fairly major refactor :'( |
Thanks for the report @vitaliyslion and for your initial thoughts @thegedge - I'll still take a look this weekend or next week and see. I'd love to understand the issue, myself. |
I think @thegedge is correct that this has to do with snapshot reactions. It looks like if a node mobx-state-tree/src/core/node/object-node.ts Line 238 in f87f96d
However, we should be running the disposer before the node dies. I think by disposing of the unobserved listener in See this modified code sandbox: https://codesandbox.io/p/sandbox/mst-onbecomeunobserved-bug-forked-pgzmv2?file=%2Fsrc%2Findex.mjs%3A48%2C1-49%2C1 import {
getObserverTree,
onBecomeObserved,
onBecomeUnobserved,
computed,
autorun,
} from "mobx";
import { destroy, types } from "mobx-state-tree";
const log = (message) => {
const element = document.createElement("div");
element.innerHTML = message;
document.body.append(message);
};
const model = types
.model({ collection$: types.array(types.frozen()) })
.actions((self) => {
let becomeObsDisposer;
let becomeUnobsDisposer;
return {
afterCreate() {
becomeObsDisposer = onBecomeObserved(self, "collection$", () => {
log("observed");
});
becomeUnobsDisposer = onBecomeUnobserved(self, "collection$", () => {
log("unobserved");
});
},
afterDestroy() {
becomeObsDisposer();
becomeUnobsDisposer();
},
};
});
const instance = model.create({ collection$: [] });
const disposeAutorun = autorun(() => instance.collection$);
disposeAutorun();
destroy(instance);
console.log(getObserverTree(instance, "collection$")); I made three changes here:
Other things you could consider:
Like @thegedge said, I don't think we can change our snapshot reaction behavior without a major change to the library, and it might even be fully breaking. Since @vitaliyslion - how do you feel about that? If you disagree, I'd be happy to keep talking about it until we get to a satisfactory resolution. I'll leave this open and if we don't hear from you in a week or so, I'll close it out (we can always reopen). |
Ah, this is trickier than I thought, even with child models. mobx-state-tree/src/core/node/object-node.ts Lines 576 to 579 in f87f96d
See here, we're still getting observed even with a parent/child tree: https://codesandbox.io/p/sandbox/mst-onbecomeunobserved-bug-forked-rztnfd?file=%2Fsrc%2Findex.mjs%3A33%2C9 But I can't see why the model's |
Ok, two new hypotheses:
I'm going to label this as |
It's not practical, correct. For our case the
I can see that reaction still exists even in that case: https://codesandbox.io/p/sandbox/mst-onbecomeunobserved-bug-forked-z3tcs8?file=%2Fsrc%2Findex.mjs%3A43%2C18&workspaceId=b264d118-589c-46f1-a79b-a83ce69dd0c6 Also, your codesanbox's links seems all to be private. Not a problem though, as you've printed the code here too. |
Hey @vitaliyslion - sorry about the privacy issues. I've made those sandboxes public now. I'm not exactly sure why the first For disposers that come from callbacks like import { getObserverTree, onBecomeObserved, onBecomeUnobserved, autorun } from 'mobx'
import { destroy, types } from 'mobx-state-tree'
const CollectionStore = types
.model({ collection$: types.array(types.frozen()) })
.volatile((self) => ({
becomeObsDisposer: onBecomeObserved(self, 'collection$', () => {
console.log('become observed')
}),
becomeUnobsDisposer: onBecomeUnobserved(self, 'collection$', () => {
console.log('become unobserved')
}),
}))
.actions((self) => {
return {
beforeDestroy() {
self.collection$.clear()
},
afterDestroy() {
self.becomeObsDisposer()
self.becomeUnobsDisposer()
},
}
})
const root = types.model({ child: CollectionStore })
const rootInstance = root.create({
child: { collection$: [{ name: '1' }, { name: '2' }] },
})
const disposeAutorun = autorun(() => {
console.log('From autorun:', rootInstance.child.collection$)
})
console.log(getObserverTree(rootInstance.child, 'collection$'))
disposeAutorun()
console.log(getObserverTree(rootInstance.child, 'collection$'))
destroy(rootInstance) The output for this code is:
See https://codesandbox.io/p/sandbox/mst-onbecomeunobserved-bug-forked-mt95z9?file=%2Fsrc%2Findex.mjs for it live. Here are some notable properties of this code snippet:
I hope this helps to clarify. At this point, this is the best recommendation I have for you in terms of directly using the |
@coolsoftwaretyler thank you for your input. I guess at this point we will at first try to redesign our app to not use |
@vitaliyslion - looks like you can use a closure over those callbacks if you want the private access pattern, just avoid using the lifecycle hook for this. I still typically prefer import {
getObserverTree,
onBecomeObserved,
onBecomeUnobserved,
autorun,
} from "mobx";
import { destroy, types } from "mobx-state-tree";
const CollectionStore = types
.model({ collection$: types.array(types.frozen()) })
.actions((self) => {
const becomeObsDisposer = onBecomeObserved(self, "collection$", () => {
console.log("become observed");
});
const becomeUnobsDisposer = onBecomeUnobserved(self, "collection$", () => {
console.log("become unobserved");
});
return {
beforeDestroy() {
self.collection$.clear();
},
afterDestroy() {
becomeObsDisposer();
becomeUnobsDisposer();
},
};
});
const root = types.model({ child: CollectionStore });
const rootInstance = root.create({
child: { collection$: [{ name: "1" }, { name: "2" }] },
});
const disposeAutorun = autorun(() => {
console.log("From autorun:", rootInstance.child.collection$);
});
console.log(getObserverTree(rootInstance.child, "collection$"));
disposeAutorun();
console.log(getObserverTree(rootInstance.child, "collection$"));
destroy(rootInstance); |
Bug report
Sandbox link or minimal reproduction code
Codesandbox
Describe the expected behavior
When autorun is disposed, an observable should become unobserved and "onBecomeUnobserved" should be called
Describe the observed behavior
"onBecomeUnobserved" is never called
Hi. Our team tries to migrate a project to MST, and we have a lot of tests that we run for our models. While examining the tests I've found that some of them are failing because onBecomeUnobserved is never called. The test looks exactly the same as in the reproduction codesandbox.
I noticed that this is also happening in the app, so the test itself seems to be fine. While using plain Mobx the same code worked well (I've actually found almost the same code in mobx's tests and took inspiration from there while writing mine).
The text was updated successfully, but these errors were encountered: