Utilize IDE's capabilities to recommend mutations and actions during the process of committing or dispatching

In my current Vue 3 Typescript project, I am utilizing Vuex. The code snippet below showcases how I have implemented it:

import { createStore, useStore as baseUseStore, Store } from 'vuex';
import { InjectionKey } from 'vue';


export interface State {
    foo: string;
}
  
export const key: InjectionKey<Store<State>> = Symbol();
  
export const store = createStore<State>({
      state: {foo: 'foo'},
      mutations: { 
        changeFoo(state: State, payload: string){
            state.foo = payload
        }
      },
      actions: { 
        setFooToBar({commit}){
          commit('changeFoo', 'bar')
      }}
})

export function useStoreTyped() {
  return baseUseStore(key);
}
  
  

Later in a component, I implement the following:

import { useStoreTyped } from "../store";

const store = useStoreTyped();

function() {
   store.distpatch('... // at this point I would like to see a list of my actions
}

This structure provides a neat feature where when I type store.state. in my IDE (VS Code), it suggests properties on my state object like .foo. However, I face an issue with mutations and actions suggestions. How can I configure the vuex store object to provide suggestions (intellisense) for these as well?

Answer №1

Is it possible to include types? Absolutely. However, the decision to add types now or wait for vuex to provide better types is a separate consideration.

Straightforward Approach

Here's a method to introduce type hints for the commit and dispatch methods of the store instance:

import { createStore, useStore as baseUseStore, Store, ActionHandler, DispatchOptions, CommitOptions } from 'vuex';
import { InjectionKey } from 'vue';


export interface State {
    foo: string;
}

export const key: InjectionKey<Store<State>> = Symbol();

const storeInitializer = {
    state: { foo: 'foo' } as State,
    mutations: {
        changeFoo(state: State, payload: string) {
            state.foo = payload
        },
        rearrangeFoo(state: State) {
            state.foo = state.foo.split('').sort().join()
        }
    },
    actions: {
        setFooToBar: (({ commit }) => {
            commit('changeFoo', 'bar')
        }) as ActionHandler<State, State>
    }
}

export type TypedDispatchAndAction<T extends { mutations: any, actions: any }> = {
  dispatch: (type: keyof T['actions'], payload?: any, options?: DispatchOptions) => Promise<any>
  commit: (type: keyof T['mutations'], payload?: any, options?:  CommitOptions) => void
}

export const store = createStore<State>(storeInitializer)

export function useStoreTyped() {
    const keyedStore = baseUseStore(key);

    return keyedStore as typeof keyedStore & TypedDispatchAndAction<typeof storeInitializer>
}

This approach has some drawbacks: it lacks elegance and doesn't offer full type checking if a mutation or action requires a payload

Elaborate Solution

This solution incorporates type hints for the key parameter of both commit and dispatch, along with type checking for the payload parameter

import { createStore, useStore as baseUseStore, Store, DispatchOptions, CommitOptions, ActionContext } from 'vuex';
import { InjectionKey } from 'vue';

type Length<L extends any[]> = L['length']

type Function<P extends any[] = any, R extends any = any> = (...args: P) => R

type ParamLength<Fn extends Function> = Length<Parameters<Fn>>

type P1<Fn extends (...args: any) => any> = Parameters<Fn>[1]

type Payload<H extends Function> = ParamLength<H> extends 2
    ? P1<H> extends infer X ? X : never
    : never

type TypedDispatchOrCommit<A extends { [key: string]: Function }, Options, Return, K extends keyof A = keyof A> = {
    <I extends K>(key: I, ...args: {
        0: [],
        1: [payload: Payload<A[I]>]
    }[Payload<A[I]> extends never ? 0 : 1]): Return
    <I extends K>(key: I, ...args: {
        0: [payload: undefined, options: Options],
        1: [payload: Payload<A[I]>, options: Options]
    }[Payload<A[I]> extends never ? 0 : 1]): Return
}

export type TypedDispatchAndAction<T extends { mutations: any, actions: any }> = {
  dispatch: TypedDispatchOrCommit<T['actions'], DispatchOptions, Promise<any>>
  commit: TypedDispatchOrCommit<T['mutations'], CommitOptions, void>
}



export interface State {
    foo: string;
}

export const key: InjectionKey<Store<State>> = Symbol();

const storeInitializer = {
    state: { foo: 'foo' } as State,
    mutations: {
        changeFoo(state: State, payload: string) {
            state.foo = payload
        },
        rearrangeFoo(state: State) {
            state.foo = state.foo.split('').sort().join()
        }
    },
    actions: {
        setFooToBar({ commit }: ActionContext<State, State>) {
            commit('changeFoo', 'bar')
        },
        setFoo({ commit }: ActionContext<State, State>, payload: string) {
            commit('changeFoo', payload)
        }
    }
}

export const store = createStore<State>(storeInitializer)

export function useStoreTyped() {
    const keyedStore = baseUseStore(key);

    return keyedStore as Omit<typeof keyedStore, 'dispatch' | 'commit'> & TypedDispatchAndAction<typeof storeInitializer>
}

While not very user-friendly, the above code does pass the following tests:

import { useTypedStore } from '../store'

const store = useTypedStore()

// expected: Pass, actual: Pass, returns void
store.commit('rearrangeFoo')
// expected: Fail, actual: Fail, returns void
store.commit('changeFoo')
// expected: Pass, actual: Pass, returns void
//    also provides typehint of string for payload
store.commit('changeFoo', 'bar')

The same principles apply for dispatch, but it outputs a Promise<any>

Final Thoughts

You can incorporate types, although the optimal scenario would involve vuex revamping their types for improved type hinting (assuming it aligns with their project objectives).

Additional Information

This code was written using vs-code along with the following packages:

typescript@^4.5.5
vuex@^4.0.2
vue@^3.2.31

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

"Utilizing Vue.js for Managing Foreign Keys in Database Entries

I am currently utilizing input in Vue.js. The structure of this input is as follows: induk_id:'', nama_barang:'', qtt:'', satuan:'', har ...

Ionic Vue Modal displays duplicated content

Trying to implement a modal in an Ionic Vue app, I encountered a strange issue where the content is being displayed twice. This problem persists even when using the example code provided on the Ionic website: The modal content component looks like this: & ...

Creating a dynamic list in Typescript from a tuple array without any intersections

const colors = ["red", "blue", "green", "yellow"] as const; const buttonSizes = ["small", "medium", "large"] as const; type ColorType = (typeof colors)[number]; type SizeType = (typeof b ...

I'm curious about the purpose of the 'name' option within the routes array in Vue.js CLI3. Can you explain

Just starting out with vue.js and I'm focusing on practicing routing at the moment. Here's a snippet of my router.js: import Vue from 'vue' import Router from 'vue-router' import Home from './views/Home.vue' Vue.u ...

Is there a more effective way to implement a Custom Validator using .forEach?

I have developed my own validation class as a learning exercise. Do you think this is an effective approach, or do you have suggestions for improvement? import { AbstractControl } from '@angular/forms'; export class ProjectNameValidator { pr ...

Leveraging the `--max-http-header-size` flag with ts-node

Encountered an issue when trying to use ts-node with the --max-http-header-size 15000 flag. It works fine with regular node, but unfortunately, ts-node does not support that option ...

Angular Form Validations - input values must not match the initial values

These are my current reactive form validations: ngOnInit(): void { this.userForm = this.formBuilder.group({ status: {checked: this.selectedUser.status == 1}, username: [this.selectedUser.username, [Validators.required, Validators.minLeng ...

How can I hide the drawer in Vuetify?

After adding navigation to the drawer, I encountered an issue where the drawer remains open when clicking on a link that goes to the same page. This can be confusing for visitors as it doesn't indicate that they are already on the current page. I att ...

What are the repercussions of labeling a function, TypeScript interface, or TypeScript type with export but never actually importing it? Is this considered poor practice or is there a potential consequence?

I find myself grappling with a seemingly straightforward question that surprisingly has not been asked before by others. I am currently immersed in a TypeScript project involving Vue, and one of the developers has taken to labeling numerous interfaces and ...

The DefaultTheme in MaterialUI no longer recognizes the 'palette' property after transitioning from v4 to v5, causing it to stop functioning correctly

Currently in the process of transitioning my app from Material UI v4 to v5 and encountering a few challenges. One issue I'm facing is that the 'palette' property is not recognized by DefaultTheme from Material UI when used in makeStyles. Thi ...

Having trouble getting Vue to properly focus on an input field

I am attempting to use this.$refs.cInput.focus() (cInput being a ref) but it seems to not be functioning as expected. I expect that when I press the 'g' key, the input field should appear and the cursor should immediately focus on it, allowing me ...

The compiler is unable to locate the node_module (Error: "Module name not found")

Error: src/app/app.component.ts:4:12 - error TS2591: Cannot find name 'module'. Do you need to install type definitions for node? Try npm i @types/node and then add node to the types field in your tsconfig. 4 moduleId: module.id, When att ...

Bringing in a Vue.js component without relying on npm installation or webpack

I'm currently working on integrating this particular component into my project: https://www.npmjs.com/package/vuejs-auto-complete The issue I've encountered is that the restriction against using npm install has posed a challenge. As our project ...

Display images in a list with a gradual fade effect as they load in Vue.js

In my Vue project, I am looking to display images one at a time with a fading effect. I have added a transition group with a fade in effect and a function that adds each image with a small delay. However, I am facing an issue where all the images show up ...

Difficulty dealing with Firestore using get() followed by add()

Recently started learning Vue.js and Firestore, facing a challenge with what should be a simple task. 1) I am trying to fetch default values from an existing template document in my Firestore database. 2) The goal is to use these default values to create ...

Uploading Images to Imgur with Angular 4

As a newcomer to TypeScript, I am faced with the challenge of uploading an image to the Imgur API using Angular. Currently, my approach involves retrieving the file from a file picker using the following code: let eventObj: MSInputMethodContext = <MSIn ...

Utilizing Vuejs to initiate a GET request using a method

Currently, I am working on enhancing the functionality of a GET request in Vue. My goal is to attach the request sending action to the form's submit button and also include a parameter from a text field within the form. HTML <html> <head> ...

The requested resource could not be located at @angular/platform-browser.js

I am attempting to set up ASP.NET MVC 5 (not Core) + Angular 2.0.0 + JSPM + SystemJS + TS Loader. Upon running the application, I encounter the following error: Failed to load resource: the server responded with a status of 404 (Not Found) http://localho ...

The type of link component that is passed in as the 'component' prop

I have created a custom Button Function Component in TypeScript using Material-UI as the base. After styling and adding features, I exported it as my own Button. Now, when I try to use this Button component in conjunction with the Link component from react ...

How to dynamically display a div in Vue.js depending on the attributes of another element

I have a dilemma with my text container. My goal is to collapse the div if the text length exceeds a certain limit, and then display a button that says "...show more". When this button is clicked, the div should expand. Clicking it again should collapse th ...