I am using Typescript, Pinia, and Vue3 and have a MenuButton
component that I want to be able to pass a Pinia store that is used for the menu open state as well as the actions to show/hide. There are a couple different menus in the app, hence why I want to be able to pass them in, and they all use the same factory to define the stores. I'm trying to figure out how to get all of this to work with typescript.
// nav.store.ts
import { defineStore } from "pinia";
import { useStorage } from "@vueuse/core";
import type { RemovableRef } from "@vueuse/core";
export interface MenuStore {
isOpen: RemovableRef<boolean>,
toggle(force?: boolean) : void,
open(): void,
close(): void,
}
interface State {
isOpen: RemovableRef<boolean>;
}
function menuStoreFactory(id: string) {
return defineStore(id, {
state: () : State => ({
isOpen: useStorage(`${id}-open`, false),
}),
actions: {
toggle(force?: boolean) {
this.isOpen = force != undefined ? force : !this.isOpen;
},
open() {
this.isOpen = true;
},
close() {
this.isOpen = false;
}
}
});
}
export const useMainMenuStore = menuStoreFactory('mainMenu');
export const useMobileMenuStore = menuStoreFactory('mobileMenu');
// setup script for the menu button component
import { MenuIcon, MenuLeftIcon } from "@/icons";
import type { MenuStore } from "@/modules/nav/nav.store";
interface Props {
controller: MenuStore
}
const props = defineProps<Props>();
Then usage is pretty straight forward:
<template>
<MenuButton
:controller="mainMenu"
></MenuButton>
</template>
<script setup lang=ts">
const mainMenu = useMainMenuStore();
</script>
With that current interface I got an error that the props didn't match. After some research I turned the interface into the following, which fixed the usage error, but then threw an error in the MenuButton
component that toggle()
and isOpen
are unresolved.
export interface MenuStore extends PiniaCustomStateProperties<{
isOpen: RemovableRef<boolean>,
toggle(force?: boolean) : void,
open(): void,
close(): void,
}> {}
Another attempted tweak that got close was:
export interface MenuStore extends Store<string, {
isOpen: RemovableRef<boolean>,
toggle(force?: boolean) : void,
open(): void,
close(): void,
}> {}
which resulted in this error on usage, but no error in the component
Type _StoreWithState<string, State, {}, {toggle(force?: boolean): void, close(): void, open(): void}> & UnwrapRef<State> & _StoreWithGetters<{}> & {toggle(force?: boolean): void, close(): void, open(): void} & PiniaCustomProperties<string, State, {}, {toggle(force?: boolean): void, close(): void, open(): void}> & PiniaCustomStateProperties<State> is not assignable to type MenuStore ... Type PiniaCustomStateProperties<State> is not assignable to type MenuStore
Your question seems to be mostly about the type/interface you need, so I'll answer that.
Instead of writing out a type yourself, you can simply use the type of the returned store.
You have a factory function that returns the return value of defineStore
, which is itself a function that returns the store. Therefore, the store's type can be found using typeof
to get the type of the factory function and then TypeScript's ReturnType
helper to drill down through the return types.
The following should be all you need:
export type MenuStore = ReturnType<ReturnType<typeof menuStoreFactory>>
Breakdown:
ReturnType< // C
ReturnType< // B
typeof menuStoreFactory // A
>
>
defineStore
. This is itself a function which returns the store.