Skip to content

Commit

Permalink
fix(vanilla): store should flush pending write triggered asynchronous…
Browse files Browse the repository at this point in the history
…ly and indirectly (#2451)

* fix(vanilla): store should flush pending write triggered asynchronously and indirectly

* add PR number to the test case

* an alternative way to flush async writes
  • Loading branch information
iwoplaza authored Mar 15, 2024
1 parent bae4725 commit 9952649
Show file tree
Hide file tree
Showing 2 changed files with 31 additions and 3 deletions.
8 changes: 5 additions & 3 deletions src/vanilla/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -560,12 +560,15 @@ export const createStore = () => {
atom: WritableAtom<Value, Args, Result>,
...args: Args
): Result => {
let isSync = true
const getter: Getter = <V>(a: Atom<V>) => returnAtomValue(readAtomState(a))
const setter: Setter = <V, As extends unknown[], R>(
a: WritableAtom<V, As, R>,
...args: As
) => {
const isSync = pendingStack.length > 0
if (!isSync) {
pendingStack.push(new Set([a]))
}
let r: R | undefined
if (isSelfAtom(atom, a)) {
if (!hasInitialValue(a)) {
Expand All @@ -581,7 +584,7 @@ export const createStore = () => {
r = writeAtomState(a as AnyWritableAtom, ...args) as R
}
if (!isSync) {
const flushed = flushPending([a])
const flushed = flushPending(pendingStack.pop()!)
if (import.meta.env?.MODE !== 'production') {
storeListenersRev2.forEach((l) =>
l({ type: 'async-write', flushed: flushed! }),
Expand All @@ -591,7 +594,6 @@ export const createStore = () => {
return r as R
}
const result = atom.write(getter, setter, ...args)
isSync = false
return result
}

Expand Down
26 changes: 26 additions & 0 deletions tests/vanilla/store.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -502,3 +502,29 @@ it('should mount once with atom creator atom (#2314)', async () => {
store.sub(atomCreatorAtom, () => {})
expect(countAtom.onMount).toHaveBeenCalledTimes(1)
})

it('should flush pending write triggered asynchronously and indirectly (#2451)', async () => {
const store = createStore()
const anAtom = atom('initial')

const callbackFn = vi.fn((_value: string) => {})
const unsub = store.sub(anAtom, () => {
callbackFn(store.get(anAtom))
})

const actionAtom = atom(null, async (_get, set) => {
await Promise.resolve() // waiting a microtask
set(indirectSetAtom)
})

const indirectSetAtom = atom(null, (_get, set) => {
set(anAtom, 'next')
})

// executing the chain reaction
await store.set(actionAtom)

expect(callbackFn).toHaveBeenCalledOnce()
expect(callbackFn).toHaveBeenCalledWith('next')
unsub()
})

0 comments on commit 9952649

Please sign in to comment.