Skip to content

Commit 7844dba

Browse files
committed
fix: async subscription do not run when mutate state synchronously after patch
1 parent 6a02b7a commit 7844dba

File tree

2 files changed

+55
-25
lines changed

2 files changed

+55
-25
lines changed

packages/pinia/__tests__/subscriptions.spec.ts

+30
Original file line numberDiff line numberDiff line change
@@ -353,5 +353,35 @@ describe('Subscriptions', () => {
353353
expect(spy1).toHaveBeenCalledTimes(1)
354354
expect(spy2).toHaveBeenCalledTimes(2)
355355
})
356+
357+
it('debugger events should not be array when subscription is not trigger by patch', () => {
358+
const store = useStore()
359+
store.$subscribe(
360+
({ type, events }) => {
361+
if (type === MutationType.direct) {
362+
expect(Array.isArray(events)).toBe(false)
363+
}
364+
},
365+
{ flush: 'sync' }
366+
)
367+
store.$patch({ user: 'Edu' })
368+
store.user = 'a'
369+
})
370+
371+
it('should trigger subscription when mutate state synchronously after patch', async () => {
372+
const store = useStore()
373+
const spy1 = vi.fn()
374+
const spy2 = vi.fn()
375+
const spy3 = vi.fn()
376+
store.$subscribe(spy1, { flush: 'sync' })
377+
store.$subscribe(spy2, { flush: 'pre' })
378+
store.$subscribe(spy3, { flush: 'post' })
379+
store.$patch({ user: 'Edu' })
380+
store.user = 'a'
381+
expect(spy1).toHaveBeenCalledTimes(2)
382+
await nextTick()
383+
expect(spy2).toHaveBeenCalledTimes(2)
384+
expect(spy3).toHaveBeenCalledTimes(2)
385+
})
356386
})
357387
})

packages/pinia/src/store.ts

+25-25
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import {
1919
toRefs,
2020
Ref,
2121
ref,
22-
nextTick,
2322
} from 'vue'
2423
import {
2524
StateTree,
@@ -247,7 +246,7 @@ function createSetupStore<
247246
if (isListening) {
248247
debuggerEvents = event
249248
// avoid triggering this while the store is being built and the state is being set in pinia
250-
} else if (isListening == false && !store._hotUpdating) {
249+
} else if (isListening === false && !store._hotUpdating) {
251250
// let patch send all the events together later
252251
/* istanbul ignore else */
253252
if (Array.isArray(debuggerEvents)) {
@@ -262,8 +261,8 @@ function createSetupStore<
262261
}
263262

264263
// internal state
265-
let isListening: boolean // set to true at the end
266-
let isSyncListening: boolean // set to true at the end
264+
let isListening = false // set to true at the end
265+
let shouldTrigger = false // The initial value does not matter, and no need to set to true at the end
267266
let subscriptions: SubscriptionCallback<S>[] = []
268267
let actionSubscriptions: StoreOnActionListener<Id, S, G, A>[] = []
269268
let debuggerEvents: DebuggerEvent[] | DebuggerEvent
@@ -278,9 +277,6 @@ function createSetupStore<
278277

279278
const hotState = ref({} as S)
280279

281-
// avoid triggering too many listeners
282-
// https://github.com/vuejs/pinia/issues/1129
283-
let activeListener: Symbol | undefined
284280
function $patch(stateMutation: (state: UnwrapRef<S>) => void): void
285281
function $patch(partialState: _DeepPartial<UnwrapRef<S>>): void
286282
function $patch(
@@ -289,7 +285,7 @@ function createSetupStore<
289285
| ((state: UnwrapRef<S>) => void)
290286
): void {
291287
let subscriptionMutation: SubscriptionCallbackMutation<S>
292-
isListening = isSyncListening = false
288+
isListening = false
293289
// reset the debugger events since patches are sync
294290
/* istanbul ignore else */
295291
if (__DEV__) {
@@ -311,13 +307,7 @@ function createSetupStore<
311307
events: debuggerEvents as DebuggerEvent[],
312308
}
313309
}
314-
const myListenerId = (activeListener = Symbol())
315-
nextTick().then(() => {
316-
if (activeListener === myListenerId) {
317-
isListening = true
318-
}
319-
})
320-
isSyncListening = true
310+
isListening = true
321311
// because we paused the watcher, we need to manually call the subscriptions
322312
triggerSubscriptions(
323313
subscriptions,
@@ -441,11 +431,19 @@ function createSetupStore<
441431
options.detached,
442432
() => stopWatcher()
443433
)
444-
const stopWatcher = scope.run(() =>
445-
watch(
434+
const stopWatcher = scope.run(() => {
435+
const stop1 = watch(
436+
() => pinia.state.value[$id],
437+
() => {
438+
shouldTrigger = isListening
439+
},
440+
{ deep: true, flush: 'sync' }
441+
)
442+
443+
const stop2 = watch(
446444
() => pinia.state.value[$id] as UnwrapRef<S>,
447445
(state) => {
448-
if (options.flush === 'sync' ? isSyncListening : isListening) {
446+
if (shouldTrigger) {
449447
callback(
450448
{
451449
storeId: $id,
@@ -458,7 +456,14 @@ function createSetupStore<
458456
},
459457
assign({}, $subscribeOptions, options)
460458
)
461-
)!
459+
460+
const stop = () => {
461+
stop1()
462+
stop2()
463+
}
464+
465+
return stop
466+
})!
462467

463468
return removeSubscription
464469
},
@@ -614,12 +619,8 @@ function createSetupStore<
614619

615620
// avoid devtools logging this as a mutation
616621
isListening = false
617-
isSyncListening = false
618622
pinia.state.value[$id] = toRef(newStore._hmrPayload, 'hotState')
619-
isSyncListening = true
620-
nextTick().then(() => {
621-
isListening = true
622-
})
623+
isListening = true
623624

624625
for (const actionName in newStore._hmrPayload.actions) {
625626
const actionFn: _Method = newStore[actionName]
@@ -748,7 +749,6 @@ function createSetupStore<
748749
}
749750

750751
isListening = true
751-
isSyncListening = true
752752
return store
753753
}
754754

0 commit comments

Comments
 (0)