Skip to content

Commit

Permalink
Skip @snapshottedView reaction when detached
Browse files Browse the repository at this point in the history
  • Loading branch information
scott-rc committed Jul 2, 2024
1 parent 0ab634a commit 21e08f7
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 4 deletions.
34 changes: 34 additions & 0 deletions spec/class-model-snapshotted-views.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { observable, runInAction } from "mobx";
import { ClassModel, action, snapshottedView, getSnapshot, register, types, onPatch } from "../src";
import { Apple } from "./fixtures/FruitAisle";
import { create } from "./helpers";
import { getParent } from "mobx-state-tree";

@register
class ViewExample extends ClassModel({ key: types.identifier, name: types.string }) {
Expand Down Expand Up @@ -91,6 +92,39 @@ describe("class model snapshotted views", () => {
expect(fn).toMatchSnapshot();
});

test("an observable instance doesn't re-compute it's view when detached", () => {
const onError = jest.fn();

@register
class Child extends ClassModel({}) {
@snapshottedView({ onError })
get parentsChildLength() {
const parent: Parent = getParent(this, 2);
return parent.children.size;
}
}

@register
class Parent extends ClassModel({ children: types.map(Child) }) {
@action
addChild(key: string) {
this.children.set(key, {});
return this.children.get(key);
}

@action
removeChild(key: string) {
this.children.delete(key);
}
}

const parent = Parent.create();
parent.addChild("foo");
parent.removeChild("foo");

expect(onError).not.toHaveBeenCalled();
});

test("an observable instance's snapshot includes the snapshotted views epoch", () => {
const instance = ViewExample.create({ key: "1", name: "Test" });
expect(getSnapshot(instance)).toEqual({ __snapshottedViewsEpoch: 0, key: "1", name: "Test" });
Expand Down
17 changes: 13 additions & 4 deletions src/class-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import "reflect-metadata";

import memoize from "lodash.memoize";
import type { Instance, IModelType as MSTIModelType, ModelActions } from "mobx-state-tree";
import { isRoot, types as mstTypes } from "mobx-state-tree";
import { isAlive, isRoot, types as mstTypes } from "mobx-state-tree";
import { RegistrationError } from "./errors";
import { InstantiatorBuilder } from "./fast-instantiator";
import { FastGetBuilder } from "./fast-getter";
import { defaultThrowAction, mstPropsFromQuickProps, propsFromModelPropsDeclaration } from "./model";
import {
$context,
$identifier,
$notAlive,
$originalDescriptor,
$parent,
$quickType,
Expand Down Expand Up @@ -48,6 +49,9 @@ export interface SnapshottedViewOptions<V, T extends IAnyClassModelType> {

/** A function for converting the view value to a snapshot value */
createSnapshot?: (value: V) => any;

/** A function that will be called when the view's reaction throws an error. */
onError?: (error: any) => void;
}

/** @internal */
Expand Down Expand Up @@ -301,6 +305,9 @@ export function register<Instance, Klass extends { new (...args: any[]): Instanc
for (const view of klass.snapshottedViews) {
reaction(
() => {
if (!isAlive(self)) {
return $notAlive;
}
const value = self[view.property];
if (view.options.createSnapshot) {
return view.options.createSnapshot(value);
Expand All @@ -310,10 +317,12 @@ export function register<Instance, Klass extends { new (...args: any[]): Instanc
}
return getSnapshot(value);
},
() => {
self.__incrementSnapshottedViewsEpoch();
(result) => {
if (result !== $notAlive) {
self.__incrementSnapshottedViewsEpoch();
}
},
{ equals: comparer.structural },
{ equals: comparer.structural, onError: view.options.onError },
);
}
},
Expand Down
3 changes: 3 additions & 0 deletions src/symbols.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ export const $readOnly = Symbol.for("MQT_readonly");
/** @hidden */
export const $originalDescriptor = Symbol.for("MQT_originalDescriptor");

/** @hidden */
export const $notAlive = Symbol.for("MQT_notAlive");

/**
* Set on an type when that type needs to be registered with a decorator before it can be used
* @hidden
Expand Down

0 comments on commit 21e08f7

Please sign in to comment.