Skip to content

Commit ac192e4

Browse files
committed
fix: async subscription do not run when mutate state synchronously after patch
1 parent 7986f8e commit ac192e4

File tree

2 files changed

+55
-25
lines changed

2 files changed

+55
-25
lines changed

packages/pinia/__tests__/subscriptions.spec.ts

Lines changed: 30 additions & 0 deletions
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

Lines changed: 25 additions & 25 deletions
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,
@@ -250,7 +249,7 @@ function createSetupStore<
250249
if (isListening) {
251250
debuggerEvents = event
252251
// avoid triggering this while the store is being built and the state is being set in pinia
253-
} else if (isListening == false && !store._hotUpdating) {
252+
} else if (isListening === false && !store._hotUpdating) {
254253
// let patch send all the events together later
255254
/* istanbul ignore else */
256255
if (Array.isArray(debuggerEvents)) {
@@ -265,8 +264,8 @@ function createSetupStore<
265264
}
266265

267266
// internal state
268-
let isListening: boolean // set to true at the end
269-
let isSyncListening: boolean // set to true at the end
267+
let isListening = false // set to true at the end
268+
let shouldTrigger = false // The initial value does not matter, and no need to set to true at the end
270269
let subscriptions: Set<SubscriptionCallback<S>> = new Set()
271270
let actionSubscriptions: Set<StoreOnActionListener<Id, S, G, A>> = new Set()
272271
let debuggerEvents: DebuggerEvent[] | DebuggerEvent
@@ -281,9 +280,6 @@ function createSetupStore<
281280

282281
const hotState = ref({} as S)
283282

284-
// avoid triggering too many listeners
285-
// https://github.com/vuejs/pinia/issues/1129
286-
let activeListener: Symbol | undefined
287283
function $patch(stateMutation: (state: UnwrapRef<S>) => void): void
288284
function $patch(partialState: _DeepPartial<UnwrapRef<S>>): void
289285
function $patch(
@@ -292,7 +288,7 @@ function createSetupStore<
292288
| ((state: UnwrapRef<S>) => void)
293289
): void {
294290
let subscriptionMutation: SubscriptionCallbackMutation<S>
295-
isListening = isSyncListening = false
291+
isListening = false
296292
// reset the debugger events since patches are sync
297293
/* istanbul ignore else */
298294
if (__DEV__) {
@@ -314,13 +310,7 @@ function createSetupStore<
314310
events: debuggerEvents as DebuggerEvent[],
315311
}
316312
}
317-
const myListenerId = (activeListener = Symbol())
318-
nextTick().then(() => {
319-
if (activeListener === myListenerId) {
320-
isListening = true
321-
}
322-
})
323-
isSyncListening = true
313+
isListening = true
324314
// because we paused the watcher, we need to manually call the subscriptions
325315
triggerSubscriptions(
326316
subscriptions,
@@ -444,11 +434,19 @@ function createSetupStore<
444434
options.detached,
445435
() => stopWatcher()
446436
)
447-
const stopWatcher = scope.run(() =>
448-
watch(
437+
const stopWatcher = scope.run(() => {
438+
const stop1 = watch(
439+
() => pinia.state.value[$id],
440+
() => {
441+
shouldTrigger = isListening
442+
},
443+
{ deep: true, flush: 'sync' }
444+
)
445+
446+
const stop2 = watch(
449447
() => pinia.state.value[$id] as UnwrapRef<S>,
450448
(state) => {
451-
if (options.flush === 'sync' ? isSyncListening : isListening) {
449+
if (shouldTrigger) {
452450
callback(
453451
{
454452
storeId: $id,
@@ -461,7 +459,14 @@ function createSetupStore<
461459
},
462460
assign({}, $subscribeOptions, options)
463461
)
464-
)!
462+
463+
const stop = () => {
464+
stop1()
465+
stop2()
466+
}
467+
468+
return stop
469+
})!
465470

466471
return removeSubscription
467472
},
@@ -617,12 +622,8 @@ function createSetupStore<
617622

618623
// avoid devtools logging this as a mutation
619624
isListening = false
620-
isSyncListening = false
621625
pinia.state.value[$id] = toRef(newStore._hmrPayload, 'hotState')
622-
isSyncListening = true
623-
nextTick().then(() => {
624-
isListening = true
625-
})
626+
isListening = true
626627

627628
for (const actionName in newStore._hmrPayload.actions) {
628629
const actionFn: _Method = newStore[actionName]
@@ -751,7 +752,6 @@ function createSetupStore<
751752
}
752753

753754
isListening = true
754-
isSyncListening = true
755755
return store
756756
}
757757

0 commit comments

Comments
 (0)