Creating base pinia action that can be mixed-in to several stores with typescript
I am trying to create generic actions that can be mixed-in to several pinia stores. However I don't manage to make the types work. Consider the following base state and actions :
import { Store, defineStore } from 'pinia'
interface Actions<T> {
a1: (this: _Store<T>) => void
a2: (this: _Store<T>) => void
}
interface State {}
interface Getters {}
type _Store<T> = Store<
string,
State,
Getters,
Actions<T>
>
const makeActions = <T>(): Actions<T> => ({
a1() {
// $patch is available here
this.$patch({})
},
a2() {
// a1 is available here
this.a1()
}
})
Then, seems simple enough ... however if I try to create a store that extends these actions :
const useMyStore = defineStore('MyStore', {
state: (): State => ({}),
actions: {
...makeActions<number>(),
a3(this: _Store<number>) {
this.$patch({})
this.a1()
},
},
})
Then when using it, I'm getting an error, because the this
of the extended store doesn't correspond with the this
of the base actions.
const store = useMyStore()
// TS error :
// 'this' context of type
// 'Store<"MyStore", MyState, {}, {
// a3(this: Store<number>): void;
// a1: (this: _Store<number>) => void;
// a2: (this: _Store<number>) => void; }
// >'
// is not assignable to method's 'this' of type '_Store<number>'.
store.a1()
Any idea how to fix these types elegantly ?
Answers
To fix the type error and ensure that the this
context of your actions matches the extended store's type, you can adjust the definition of your makeActions
function to use a generic constraint. Here's how you can do it:
import { Store, defineStore } from 'pinia'
interface Actions<T> {
a1: (this: _Store<T>) => void
a2: (this: _Store<T>) => void
}
interface State {}
interface Getters {}
type _Store<T> = Store<
string,
State,
Getters,
Actions<T>
>
const makeActions = <T extends _Store<any>>(): Actions<T['$type']> => ({
a1() {
// $patch is available here
this.$patch({})
},
a2() {
// a1 is available here
this.a1()
}
})
const useMyStore = defineStore('MyStore', {
state: (): State => ({}),
actions: makeActions<_Store<number>>(),
})
In this adjustment:
- The
makeActions
function is now constrained with<T extends _Store<any>>
, which ensures thatT
must be a subtype of_Store<any>
. - When calling
makeActions<_Store<number>>()
, the typeT
will be inferred as_Store<number>
. - Inside the
makeActions
function,T['$type']
is used to extract the type of the store, allowing you to use it as the type for your actions. - This ensures that the
this
context of your actions matches the type of the extended store, resolving the type error you were encountering.