diff --git a/.changeset/witty-falcons-explode.md b/.changeset/witty-falcons-explode.md new file mode 100644 index 000000000..f48924f76 --- /dev/null +++ b/.changeset/witty-falcons-explode.md @@ -0,0 +1,5 @@ +--- +"mobx": patch +--- + +Fixed: #3747, computed values becoming stale if the underlying observable was created and updated outside a reactive context diff --git a/packages/mobx/__tests__/v5/base/observables.js b/packages/mobx/__tests__/v5/base/observables.js index 86c5d7e0f..40e28949a 100644 --- a/packages/mobx/__tests__/v5/base/observables.js +++ b/packages/mobx/__tests__/v5/base/observables.js @@ -2382,6 +2382,7 @@ test("state version updates correctly", () => { o.x++ }) + // expect(o.x).toBe(4) is 1? prevStateVersion = getGlobalState().stateVersion o.x++ expect(o.x).toBe(5) @@ -2505,3 +2506,13 @@ test("state version does not update on observable creation", () => { check(() => mobx.observable([0], { proxy: true })) check(() => mobx.computed(() => 0)) }) + +test("#3747", () => { + mobx.runInAction(() => { + const o = observable.box(0) + const c = computed(() => o.get()) + expect(c.get()).toBe(0) + o.set(1) + expect(c.get()).toBe(1) // would fail + }) +}) diff --git a/packages/mobx/src/core/atom.ts b/packages/mobx/src/core/atom.ts index c9e5315ad..673e7687a 100644 --- a/packages/mobx/src/core/atom.ts +++ b/packages/mobx/src/core/atom.ts @@ -68,21 +68,19 @@ export class Atom implements IAtom { * Invoke this method _after_ this method has changed to signal mobx that all its observers should invalidate. */ public reportChanged() { - if (globalState.inBatch && this.batchId_ === globalState.batchId) { - // Called from the same batch this atom was created in. - return + if (!globalState.inBatch || this.batchId_ !== globalState.batchId) { + // We could update state version only at the end of batch, + // but we would still have to switch some global flag here to signal a change. + globalState.stateVersion = + globalState.stateVersion < Number.MAX_SAFE_INTEGER + ? globalState.stateVersion + 1 + : Number.MIN_SAFE_INTEGER + // Avoids the possibility of hitting the same globalState.batchId when it cycled through all integers (necessary?) + this.batchId_ = NaN } - // Avoids the possibility of hitting the same globalState.batchId when it cycled through all integers (necessary?) - this.batchId_ = NaN startBatch() propagateChanged(this) - // We could update state version only at the end of batch, - // but we would still have to switch some global flag here to signal a change. - globalState.stateVersion = - globalState.stateVersion < Number.MAX_SAFE_INTEGER - ? globalState.stateVersion + 1 - : Number.MIN_SAFE_INTEGER endBatch() }