Skip to content

Commit e10ae8f

Browse files
committed
fix: async subscription do not run when mutate state synchronously after patch
1 parent 9182fdb commit e10ae8f

File tree

2 files changed

+41
-30
lines changed

2 files changed

+41
-30
lines changed

packages/pinia/__tests__/subscriptions.spec.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -368,12 +368,23 @@ describe('Subscriptions', () => {
368368
store.user = 'a'
369369
expect(syncSpy).toHaveBeenCalledTimes(2)
370370

371-
// FIXME: ideally, these should be 2 but we cannot use
372-
// a sync flush within the store's $subscribe method
373-
// https://github.com/vuejs/pinia/issues/610
374371
await nextTick()
375-
expect(preSpy).toHaveBeenCalledTimes(1)
376-
expect(postSpy).toHaveBeenCalledTimes(1)
372+
expect(preSpy).toHaveBeenCalledTimes(2)
373+
expect(postSpy).toHaveBeenCalledTimes(2)
374+
})
375+
376+
it('debuggerEvents is not an array when subscription is not triggered by patch', () => {
377+
const store = useStore()
378+
store.$subscribe(
379+
({ type, events }) => {
380+
if (type === MutationType.direct) {
381+
expect(Array.isArray(events)).toBe(false)
382+
}
383+
},
384+
{ flush: 'sync' }
385+
)
386+
store.$patch({ user: 'Edu' })
387+
store.user = 'a'
377388
})
378389
})
379390
})

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)