Search code examples
typescriptvue.jsvuejs3pinia

Passing a Pinia Store as a Prop with Vue3 and TypeScript


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 

Solution

  • 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
      >
    >
    
    • A: Get the type of the factory function.
    • B: Get the return type of the factory function, which is the return from defineStore. This is itself a function which returns the store.
    • C: Get the return type of said function. This is the type of the store itself.