incorrect detaches from root store when applying snapshot
ZFail opened this issue · comments
this simple test produces multiple onAttachedToRootStore hook calls:
test('reattach', () => {
let attachCount = 0
let detachCount = 0
@model("A")
class A extends Model({
id: idProp.withSetter()
}) {
protected onAttachedToRootStore() {
++attachCount
return () => {
++detachCount
}
}
}
@model("B")
class B extends Model({
id: idProp,
a: tProp(types.model(A)),
}) {
}
const b = new B({id: 'b', a: new A({id: 'a'})})
registerRootStore(b)
const snapshot = getSnapshot(b)
b.a.setId('a2')
applySnapshot(b, snapshot)
unregisterRootStore(b)
expect(attachCount).toBe(1)
expect(detachCount).toBe(1)
})
if i remove b.a.setId('a2')
or applySnapshot
call, this test produces only one onAttachedToRootStore hook call
is this correct or bug? in my case this additional hook calls broke my change state logic (in my code, attach hooks are actively used to initiate different processes and stop them in detach hook)
This is what's happening:
import {
idProp,
model,
Model,
registerRootStore,
tProp,
types,
getSnapshot,
applySnapshot,
unregisterRootStore
} from "mobx-keystone";
console.clear();
@model("A")
class A extends Model({
id: idProp.withSetter()
}) {
protected onAttachedToRootStore() {
console.log("attach");
return () => {
console.log("detatch");
};
}
}
@model("B")
class B extends Model({
id: idProp,
a: tProp(types.model(A))
}) {}
const b = new B({ id: "b", a: new A({ id: "a" }) });
registerRootStore(b);
// attach // when you register B as rootstore then A becomes attached to a "new" rootstore (B in this case)
const snapshot = getSnapshot(b);
// here an old snapshot of B is saved
b.a.setId("a2");
// here a new snapshot of B.A is generated
applySnapshot(b, snapshot);
// this actually creates a new instance of B.A, since the ID of old B.A and new B.A do not match
// (if you had changed something else other than the ID then it would have been "reconcilied" rather than replaced)
// detatch // old B.A dies
// attach // new B.A is added
unregisterRootStore(b);
// detatch // the rootstore is detached, so B.A is detached from a "root store"
Thank you for detailed answer!
Now i dont change ids, but i also get multiple attach/detach
@model("A")
class A extends Model({
id: idProp.withSetter(),
n: tProp(types.number).withSetter()
}) {
protected onAttachedToRootStore() {
console.log("attach");
return () => {
console.log("detach");
};
}
}
@model("B")
class B extends Model({
id: idProp,
a: tProp(types.model(A))
}) {}
console.log("1");
const b = new B({ id: "b", a: new A({ id: "a", n: 1 }) });
console.log("2");
registerRootStore(b);
console.log("3");
const snapshot = getSnapshot(b);
console.log("4");
b.a.setN(2);
console.log(getSnapshot(b), snapshot);
applySnapshot(b, snapshot);
console.log("6");
unregisterRootStore(b);
console.log("7");
This is ok? and applySnapshot always detach all models from root store and reattaching them
That last one was indeed a bug. Fixed in 1.6.1
It works, thanks!