xaviergonz / mobx-keystone

A MobX powered state management solution based on data trees with first class support for Typescript, support for snapshots, patches and much more
https://mobx-keystone.js.org
MIT License
554 stars 25 forks source link

Exception when detaching model that have active reaction with reference #523

Closed ZFail closed 1 year ago

ZFail commented 1 year ago

this sample

const aRef = rootRef<A>("aRef");

@model("A")
class A extends Model({
  n: idProp.withSetter(),
  aref: tProp(types.maybe(types.ref(aRef))).withSetter()
}) {
  protected onAttachedToRootStore() {
    console.log("attach", this.n);
    const disposers = [
      autorun(() => {
        console.log("reaction", this.aref?.current);
      })
    ];
    return () => {
      console.log("detach", this.n);
      disposers.forEach((it) => it());
    };
  }
}

@model("B")
class B extends Model({
  as: tProp(types.array(types.model(A))).withSetter()
}) {
  @modelAction delA() {
    this.as.splice(1, 1);
  }
}

const b = new B({
  as: [new A({ n: "1" }), new A({ n: "2" })]
});
registerRootStore(b);
b.as[1].setAref(aRef(b.as[0]));
b.delA();

causes [mobx] Encountered an uncaught exception that was thrown by a reaction or observer component, in: 'Reaction[Autorun@166]' Error: a reference of type 'aRef' could not resolve an object with id '1'

xaviergonz commented 1 year ago

use

this.aref?.maybeCurrent

instead of

this.aref?.current

or use isValid before accessing current if the referenced object might cease to exist

ZFail commented 1 year ago

why reference is not valid? i`m deleting model with id '2'? not '1'

xaviergonz commented 1 year ago

Because a[1] holds a reference to id "1" through the rootstore (that's why it is called a "root" reference), but when a[1] gets detached from the root store then a[1] cannot "reach" a[0] (id: 1) through its root (which has become none) and therefore the reference becomes null

ZFail commented 1 year ago

thanks