Open sisp opened 4 years ago
The more I think about it, the more it feels like the idea of a sandbox in which actions can be performed contradicts the idea of computed properties and that's why the above error occurs. But if that's the case, I still believe that it's a valid use case to have a computed property that needs to run actions (on a copy of the state) in order to determine its return value because it must simulate a possible future state and evaluate the result. Then, the computation performed in the computed property is not purely derivative, but at the same time there is no permanent state mutation if the state copy is discarded or put back in sync with the original state. I'm a bit stuck now seeing options how to make this possible, though.
Yeah, the weird thing here is that a computed is not supposed to alter state, and withSandbox is supposed to alter state, so they are contradictory. Do you really need it to be computed?
Maybe in this case it should really be a reaction/autorun + observable volatile?
@observable times2?: number
onInit() { // maybe onAttachedToRootStore?
// this should automatically run everytime tracked (used) values change
// in this case if this.a.b or sandobox.b.value changes
// if it shouldn't autorun when sandbox.b.value changes then either
// make the callback to withSandbox an action (which is untracked)
// or use a reaction and specify explicitely what needs to be tracked
const disp = autorun(() => {
const ctx = sandboxContext.get(this)
if (!ctx) return
this.times2 = ctx.withSandbox(this.a.b, b => {
b.setValue(b.value * 2)
return { commit: false, return: b.value }
})
})
return disp;
}
...
whatever.times2
?
That's more or less how I'm doing it right now to work around the problem with a computed property. It just feels like a workaround.
By the way, the workaround using autorun
results in a cycle:
test("observables in sandbox are tracked (autorun)", () => {
const sandboxContext = createContext<SandboxManager>()
@model("R2")
class R extends Model({ a: prop<A>() }) {
@observable
times2!: number
onAttachedToRootStore() {
return autorun(() => {
const ctx = sandboxContext.get(this)
if (!ctx) return
const result = ctx.withSandbox(this.a.b, b => {
b.setValue(b.value * 2)
return { commit: false, return: b.value }
})
runInAction(() => {
this.times2 = result
})
})
}
}
const r = new R({ a: new A({ b: new B({ value: 0 }) }) })
autoDispose(() => {
if (isRootStore(r)) {
unregisterRootStore(r)
}
})
const manager = sandbox(r)
autoDispose(() => manager.dispose())
sandboxContext.set(r, manager)
registerRootStore(r)
const times2: number[] = []
autoDispose(
reaction(
() => r.times2,
t2 => times2.push(t2)
)
)
r.a.b.setValue(2)
expect(times2).toEqual([4])
})
Reaction doesn't converge to a stable state after 100 iterations. Probably there is a cycle in the reactive function: Reaction[Autorun@314]
I can make it work using reaction
if I know the exact dependencies, but automated dependency tracking would be safer and more convenient.
Interestingly, this (computed property with sandbox) works with both autorun
and reaction
:
test("observables in sandbox are tracked", () => {
const sandboxContext = createContext<SandboxManager>()
@model("R2")
class R extends Model({ a: prop<A>(), x: prop<number>() }) {
@computed
get times2(): number {
return computed(() => { // equivalent to `expr` from `mobx-utils`
const ctx = sandboxContext.get(this)
if (!ctx) {
throw new Error("sandbox context required")
}
let result!: number
_allowStateChangesInsideComputed(() => { // from `mobx`
result = ctx.withSandbox(this.a.b, b => {
b.setValue(b.value * 2)
return { commit: false, return: b.value }
})
})
return result
}).get()
}
@modelAction
incX(): void {
this.x++
}
}
const r = new R({ a: new A({ b: new B({ value: 0 }) }), x: 0 })
const manager = sandbox(r)
autoDispose(() => manager.dispose())
sandboxContext.set(r, manager)
const times2: number[] = []
autoDispose(autorun(() => times2.push(r.times2)))
r.a.b.setValue(2)
r.a.b.setValue(3)
r.incX() // should trigger no reaction of `r.times2`
expect(times2).toEqual([0, 4, 6])
})
I'm not exactly sure why. Do you know?
A computed property which uses a sandbox to determine its return value is not tracked. The test is meant to be included in
packages/lib/tests/treeUtils/sandbox.test.ts
:This test results in the following error: