Search code examples
typescriptvuejs3pinia

How to correctly implement Typing to proxy object in Pinia


I'm using Vue3 (setup script in composition API style), Typescript and Pinia (in Option API style) and I can't figure out how to correctly type the elements in my state. Here is an exemple of my missunderstanding:

state.ts :

interface myObject {
  id: number
  name: string
}
interface State {
  myFavColor: string
  objList: myObject[]
}

export const useMyStore = defineStore("myStore", {
  state: (): State => ({
    myFavColor: "orange",
    objList: objService.fetchObjList(),
  }),
  getters: {
    gimmeMyFavColor: (state): string => state.myFavColor,
    gimmeObjList: (state): myObject[] => state.objList
  },
  actions: {},
});

myComp.vue

<script setup>
const myFavCol: string = useMyStore.gimmeMyFavColor
console.log('myFavCol', myFavCol, typeof myFavCol)
// -> 'myFavCol', orange, string


const myObjList: myObject[] = useMyStore.gimmeObjList
// Got a typing error there
console.log('myObjList', myObjList, typeof myObjList)
// -> 'myObjList', [...], Proxy(Array)
</script>

There I've already got an issue of typing with the gimmeObjList getter and if I use the same getter in a component waiting for a type of myObject[], there is the same issue. The point is that strings still strings (same for others primitives) but arrays of objects become Proxy(Array) (and Objects become Proxy Objects).

I'd like to use reactivity and using toRaw() doesn't fix the typing issue (I've read a lot of docs and Stackoverflow/github posts but I maybe I missed something)

--EDIT--

Here is a concrete example of my issue. There is a store trying to handle the Map object from the Openlayers lib. With the code of the mapStore below:

import type { Map } from "ol";
import { defineStore } from "pinia";

interface State {
  map: Map | null;
}

export const useMapStore = defineStore("mapStore", {
  state: (): State => ({
    map: null,
  }),
  getters: {
    storedMap: (state): Map | null => state.map
  }
});

I've got the following error in my IDE:

Type '{ on: MapEventHandler<EventsKey>; once: MapEventHandler<EventsKey>; un: MapEventHandler<void>; resizeObserver_: { ...; }; ... 67 more ...; dispose: () => void; } | null' is not assignable to type 'Map | null'.
  Type '{ on: MapEventHandler<EventsKey>; once: MapEventHandler<EventsKey>; un: MapEventHandler<void>; resizeObserver_: { ...; }; ... 67 more ...; dispose: () => void; }' is missing the following properties from type 'Map': renderComplete_, loaded_, boundHandleBrowserEvent_, maxTilesLoading_, and 51 more.ts(2322)

What we can notice is, if I remove the Map | null type of the answer of the getter, there is no longer any error. That's why I really don't understand how to type correctly complexe objects in the Pinia stores.


Solution

  • to use a store in pinia after importing it (Use a store in pinia).

    You need to call it with bracket. It works fine for me like that :

    store.ts :

     import { defineStore } from "pinia";
    
      export interface myObject {
        id: number
        name: string
      }
      interface State {
        myFavColor: string
        objList: myObject[]
      }
      
      export const useMyStore = defineStore("myStore", {
        state: (): State => ({
          myFavColor: "orange",
          objList: [{id: 1, name: "one"}, {id: 2, name: "two"}], // Sample of list which respect the interface
        }),
        getters: {
          gimmeMyFavColor: (state): string => state.myFavColor,
          gimmeObjList: (state): myObject[] => state.objList
        },
        actions: {},
      });
    

    Comp.vue :

    <script setup lang="ts">
    import { useMyStore, myObject } from './store'
    const myFavCol: string = useMyStore().gimmeMyFavColor
    console.log('myFavCol', myFavCol, typeof myFavCol)
    // -> 'myFavCol', orange, string
    
    
    const myObjList: myObject[] = useMyStore().gimmeObjList
    // Got a typing error there
    console.log('myObjList', myObjList, typeof myObjList)
    // -> 'myObjList', [...], Proxy(Array)
    </script>
    
    <template>
      <h1>Test Store</h1>
      <p>myFavCol: {{ myFavCol }}</p>
      <p>myObjList: {{ myObjList }}</p>
    </template>
    

    I don't get any type errors and the list is displayed correctly, so I hope it solves your problems, if not you need to add more details about what's happening wrong please.


    EDIT

    You need to/can cast your state.map, even if I don't see the affinity with the original problem:

    store.ts

    import { Map } from "ol";
    import { defineStore } from "pinia";
    
    interface State {
      map: Map | null;
    }
    
    export const useMapStore = defineStore("mapStore", {
      state: (): State => ({
        map: null,
      }),
      getters: {
        storedMap: (state): Map | null => state.map as Map, // <-- HERE
      },
    });