DEV Community

Tim Bendt
Tim Bendt

Posted on

Type-safe Vuex State Usage in Components (Without [as many] Decorators)

If you've started a Vue project and used Typescript recently you are probably familiar with the vue-class-component or maybe the more extensive alternative, vue-property-decorator. These libraries provide decorators, which are currently supported in Typescript, to allow typescript developers to write vue components using a ES6 class syntax and a declarative "Decorator" syntax which picks up a property or method and wraps it in specific functionality, like @Prop before a plain class member makes the decorated property a member of the final component props object during runtime.


import { Component, Prop, Vue } from "vue-property-decorator";

@Component
export default class PhotoCard extends Vue {
  @Prop()
  public url!: string;

  public imageSrc: string = this.url || "";
  public isEditing: boolean = false;

  public editPhoto() {
    this.isEditing = true;
  }

  public cancel() {
    this.isEditing = false;
  }
}

I can't say for sure that the decorator way of writing Vue components is completely superior to the classic way of defining components using Vue.component(), but it does make the syntax a little more flexible and more type-safe when you are referencing other parts of the component from inside it's members.

Now, once you've decided to dive into the "typescript way" of writing Vue apps you may find yourself wondering how to make everything typesafe. This comes up quickly when you are dealing with Vuex. Because, Vuex is a fairly "indirect" programming paradigm. You have to infer the presence of state values by looking at the source of the Vuex store itself. And you have to map to mutations and actions using "magic strings" inside of your component code. 🤮

You may have seen the project vuex-class which makes it possible to map members of your component to your store using decorators, but this doesn't really offer you a truly type-safe coupling between the store and it's usage in the components. It's still "indirect". You have to define the parameter types and return types yourself inside the component by looking at the source code of the vuex store. Eg:


    const authModule = namespace("auth");

  // ...

    @authModule.State
    public authenticated: boolean;

    @authModule.State
    public user: IUser;

    @authModule.Action
    public handleReset: (email: string) => Promise<any>;

    public handleResetPassword() {
      this.handleReset(this.user.email).then(() => {
        this.showResetInstruction = true;
      });
    }

I discovered a better library for solving this problem recently and refactored a project to use it, so, I will share with you how to use the vuex-module-decorators instead.

This approach works best if you are writing modules to break up your vuex store into multiple code files.

When you write and define your store modules you will use a class decorator just like when you write a vue class component.

import { Module, VuexModule, Mutation, Action } from "vuex-module-decorators";
import Store from "../index";

@Module({
    dynamic: true,
    store: Store,
    name: "userAdmin",
    namespaced: true,
})
export default class UserAdminModule extends VuexModule {

    public userResults: GetUserDto[] = [];

    public rawResultSet: GetUserDto[] = [];

// ... 

    public get historyForUser(): GetAuditDto[] {
//        ...
    }

    @Mutation
    public loadResults({ data }: AxiosResponse<GetUserDto[]>) {
        if (data && data.length) {
            this.rawResultSet = data;
        }
    }

    @Action({ commit: "loadResults"})
    public async searchUsers(organization: string) {
        return await ApiClient.getUsers({ organization: organization || "1", sort: "name"});
    }
}

This module will be inserted into the parent store dynamically at run-time. Any "getter" in the class will be turned into a member of the "getters" on the vuex module. The @Mutation decorator does what it says on the tin. Inside a mutation function you modify properties of the store by using the friendly and strongly typed this. syntax. And the @Action decorator will automatically call the specified mutation after the promise resolves from the async function it is decorating, cutting down on the boilerplate code of writing actions that call mutations when they are done.

So... how do you access this module in a typesafe way on your components?

Like this. You use the getModule() function in your component to get a typed instance of the module.

import { getModule } from "vuex-module-decorators";
import UserAdmin from "@/store/modules/UserAdmin";

const userAdminState = getModule(UserAdmin);

@Component({
  components: {
    // ...
  },
})
export default class AdminUsersSearch extends Vue {
  public loading: boolean = false;

  public get filteredUsers(): GetUserDto[] {
    return userAdminState.filteredResults;
  }

  public mounted() {
    this.loading = true;
    userAdminState.searchUsers("1").then(x => this.loading = false);
  }

}

Note: if you didn't create a "dynamic" module you will need to pass the store into the getModule function - see the docs.

In your typescript code, as soon as you type userAdminState. you immediately get intellisense (in VS Code obv.) which shows you what the state, getters, mutations, and actions are available on the state module. If you need to change some property of the vuex module, you will get compiler errors everywhere that you use that property of the module in any component, making it harder to break your app when you refactor your state code.

Top comments (2)

Collapse
 
ronnyek profile image
Weston

Do you happen to have example of this in a repo or stackblitz or something like that? Generally code in examples provided look good, but I'm unable to get this code working presumably because I've got the remaining configuration incorrect.

Starting out with what vue-cli generates on the fly for me.

Collapse
 
dyllandry profile image
Dylan Landry

If your fourth example, how could you get the module's state?