diff --git a/src/vanilla/store.ts b/src/vanilla/store.ts index 6c20762127..fcc2fb7ea7 100644 --- a/src/vanilla/store.ts +++ b/src/vanilla/store.ts @@ -115,7 +115,7 @@ type AtomState = { /** Atom error */ e?: AnyError /** Indicates that the atom value has been changed */ - x?: true + x: number } const isAtomStateInitialized = (atomState: AtomState) => @@ -290,7 +290,7 @@ const buildStore = (...storeArgs: StoreArgs): Store => { } let atomState = getAtomState(atom) if (!atomState) { - atomState = { d: new Map(), p: new Set(), n: 0 } + atomState = { d: new Map(), p: new Set(), n: 0, x: 0 } setAtomState(atom, atomState) atomOnInit?.(atom, store) } @@ -315,7 +315,6 @@ const buildStore = (...storeArgs: StoreArgs): Store => { atomState.v = valueOrPromise } delete atomState.e - delete atomState.x if (!hasPrevValue || !Object.is(prevValue, atomState.v)) { ++atomState.n if (pendingPromise) { @@ -428,7 +427,6 @@ const buildStore = (...storeArgs: StoreArgs): Store => { } catch (error) { delete atomState.v atomState.e = error - delete atomState.x ++atomState.n return atomState } finally { @@ -493,7 +491,7 @@ const buildStore = (...storeArgs: StoreArgs): Store => { // Atom has been visited but not yet processed visited.add(a) // Mark atom dirty - aState.x = true + ++aState.x stack.pop() continue } @@ -527,7 +525,7 @@ const buildStore = (...storeArgs: StoreArgs): Store => { changedAtoms.add(a) } } - delete aState.x + --aState.x } } addBatchFunc(batch, 0, finishRecompute) diff --git a/tests/vanilla/store.test.tsx b/tests/vanilla/store.test.tsx index 425063617e..dd81545d5a 100644 --- a/tests/vanilla/store.test.tsx +++ b/tests/vanilla/store.test.tsx @@ -1091,7 +1091,6 @@ it('recomputes dependents of unmounted atoms', () => { const a = atom(0) a.debugLabel = 'a' const bRead = vi.fn((get: Getter) => { - console.log('bRead') return get(a) }) const b = atom(bRead) @@ -1108,3 +1107,69 @@ it('recomputes dependents of unmounted atoms', () => { store.set(w) expect(bRead).not.toHaveBeenCalled() }) + +it('recomputes all changed atom dependents together', async () => { + const a = atom([0]) + const b = atom([0]) + const a0 = atom((get) => get(a)[0]!) + const b0 = atom((get) => get(b)[0]!) + const a0b0 = atom((get) => [get(a0), get(b0)]) + const w = atom(null, (_, set) => { + set(a, [0]) + set(b, [1]) + }) + const store = createStore() + store.sub(a0b0, () => {}) + store.set(w) + expect(store.get(a0)).toBe(0) + expect(store.get(b0)).toBe(1) + expect(store.get(a0b0)).toEqual([0, 1]) +}) + +it('runs recomputeDependents on atoms in the correct order', async () => { + const store = createStore() + let i = 0 + function createHistoryAtoms(initialValue: T) { + const historyStackAtom = atom([initialValue]) + historyStackAtom.debugLabel = `${i}:historyStackAtom` + const historyIndexAtom = atom(0) + historyIndexAtom.debugLabel = `${i}:historyIndexAtom` + const valueAtom = atom( + (get) => get(historyStackAtom)[get(historyIndexAtom)]!, + ) + valueAtom.debugLabel = `${i}:valueAtom` + const resetAtom = atom(null, (_, set, value: T) => { + set(historyStackAtom, [value]) + set(historyIndexAtom, 0) + }) + resetAtom.debugLabel = `${i}:resetAtom` + i++ + return { valueAtom, resetAtom } + } + const val1Atoms = createHistoryAtoms('foo') + const val2Atoms = createHistoryAtoms(null) + const initAtom = atom(null, (_get, set) => { + // if comment out this line, the test will pass + set(val2Atoms.resetAtom, null) + set(val1Atoms.resetAtom, 'bar') + }) + initAtom.debugLabel = 'initAtom' + const computedValAtom = atom((get) => { + const v2Value = get(val2Atoms.valueAtom) + if (v2Value !== null) { + return v2Value + } + const v1Value = get(val1Atoms.valueAtom) + return v1Value + }) + computedValAtom.debugLabel = 'computedValAtom' + const asyncInitAtom = atom(null, async (_get, set) => { + // if comment out this line, the test will pass [DOES NOT WORK] + await new Promise((resolve) => setTimeout(resolve, 0)) + set(initAtom) + }) + store.sub(computedValAtom, () => {}) + await store.set(asyncInitAtom) + const result = store.get(computedValAtom) + expect(result).toBe('bar') +})