Creating base pinia action that can be mixed-in to several store

ghz 8months ago ⋅ 99 views

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 that T must be a subtype of _Store<any>.
  • When calling makeActions<_Store<number>>(), the type T 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.