Skip to content

Commit a9ef6b6

Browse files
committed
fix(subscribe): direct mutation should not trigger detached subscriptions
Fix #908
1 parent 33e25ff commit a9ef6b6

File tree

5 files changed

+44
-16
lines changed

5 files changed

+44
-16
lines changed

packages/pinia/__tests__/subscriptions.spec.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,20 +124,33 @@ describe('Subscriptions', () => {
124124
expect(spy2).toHaveBeenCalledTimes(0)
125125

126126
s1.name = 'Edu'
127-
128127
expect(spy1).toHaveBeenCalledTimes(1)
129128
expect(spy2).toHaveBeenCalledTimes(1)
130129

131130
s1.$patch({ name: 'a' })
132131
expect(spy1).toHaveBeenCalledTimes(2)
133132
expect(spy2).toHaveBeenCalledTimes(2)
134133

134+
s1.$patch((state) => {
135+
state.name = 'other'
136+
})
137+
expect(spy1).toHaveBeenCalledTimes(3)
138+
expect(spy2).toHaveBeenCalledTimes(3)
139+
135140
wrapper.unmount()
136141
await nextTick()
137142

138143
s1.$patch({ name: 'b' })
139-
expect(spy1).toHaveBeenCalledTimes(2)
140-
expect(spy2).toHaveBeenCalledTimes(3)
144+
expect(spy1).toHaveBeenCalledTimes(3)
145+
expect(spy2).toHaveBeenCalledTimes(4)
146+
s1.$patch((state) => {
147+
state.name = 'c'
148+
})
149+
expect(spy1).toHaveBeenCalledTimes(3)
150+
expect(spy2).toHaveBeenCalledTimes(5)
151+
s1.name = 'd'
152+
expect(spy1).toHaveBeenCalledTimes(3)
153+
expect(spy2).toHaveBeenCalledTimes(6)
141154
})
142155
})
143156

packages/pinia/src/store.ts

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ import {
4848
import { setActivePinia, piniaSymbol, Pinia, activePinia } from './rootStore'
4949
import { IS_CLIENT } from './env'
5050
import { patchObject } from './hmr'
51-
import { addSubscription, triggerSubscriptions } from './subscriptions'
51+
import { addSubscription, triggerSubscriptions, noop } from './subscriptions'
5252

5353
type _ArrayType<AT> = AT extends Array<infer T> ? T : never
5454

@@ -173,8 +173,6 @@ function createOptionsStore<
173173
return store as any
174174
}
175175

176-
const noop = () => {}
177-
178176
function createSetupStore<
179177
Id extends string,
180178
SS,
@@ -376,10 +374,11 @@ function createSetupStore<
376374
$patch,
377375
$reset,
378376
$subscribe(callback, options = {}) {
379-
const _removeSubscription = addSubscription(
377+
const removeSubscription = addSubscription(
380378
subscriptions,
381379
callback,
382-
options.detached
380+
options.detached,
381+
() => stopWatcher()
383382
)
384383
const stopWatcher = scope.run(() =>
385384
watch(
@@ -400,11 +399,6 @@ function createSetupStore<
400399
)
401400
)!
402401

403-
const removeSubscription = () => {
404-
stopWatcher()
405-
_removeSubscription()
406-
}
407-
408402
return removeSubscription
409403
},
410404
$dispose,

packages/pinia/src/subscriptions.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
import { getCurrentInstance, onUnmounted } from 'vue-demi'
22
import { _Method } from './types'
33

4+
export const noop = () => {}
5+
46
export function addSubscription<T extends _Method>(
57
subscriptions: T[],
68
callback: T,
7-
detached?: boolean
9+
detached?: boolean,
10+
onCleanup: () => void = noop
811
) {
912
subscriptions.push(callback)
1013

1114
const removeSubscription = () => {
1215
const idx = subscriptions.indexOf(callback)
1316
if (idx > -1) {
1417
subscriptions.splice(idx, 1)
18+
onCleanup()
1519
}
1620
}
1721

packages/pinia/src/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -333,11 +333,11 @@ export interface _StoreWithState<
333333
/**
334334
* Group multiple changes into one function. Useful when mutating objects like
335335
* Sets or arrays and applying an object patch isn't practical, e.g. appending
336-
* to an array.
336+
* to an array. The function passed to `$patch()` **must be synchronous**.
337337
*
338338
* @param stateMutator - function that mutates `state`, cannot be async
339339
*/
340-
$patch<F extends (state: UnwrapRef<S>) => void>(
340+
$patch<F extends (state: UnwrapRef<S>) => any>(
341341
// this prevents the user from using `async` which isn't allowed
342342
stateMutator: ReturnType<F> extends Promise<any> ? never : F
343343
): void

packages/pinia/test-dts/state.test-d.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ const useStore = defineStore({
5353

5454
const store = useStore()
5555

56+
store.$patch({ counter: 2 })
57+
store.$patch((state) => {
58+
expectType<number>(state.counter)
59+
})
60+
5661
expectType<number>(store.$state.counter)
5762
expectType<number>(store.$state.double)
5863

@@ -62,3 +67,15 @@ expectType<number>(store.fromARef)
6267

6368
expectType<{ msg: string }>(store.aShallowRef)
6469
expectType<{ msg: string }>(store.$state.aShallowRef)
70+
71+
const onlyState = defineStore({
72+
id: 'main',
73+
state: () => ({
74+
counter: 0,
75+
}),
76+
})()
77+
78+
onlyState.$patch({ counter: 2 })
79+
onlyState.$patch((state) => {
80+
expectType<number>(state.counter)
81+
})

0 commit comments

Comments
 (0)