Strongly-typed Vuex Store in NuxtJS

July 31st 2020 Vuex TypeScript NuxtJS

Vuex store code can be quite verbose, especially with wrappers for type-safety in TypeScript. A lot of that plumbing can be avoided with the vuex-module-decorators package. There's some extra configuration required to get it working in NuxtJS.

The core module definition for NuxtJS is the same as in plain Vue.js and is well documented:

  • The store module is defined as a class extending VuexModule with @Module decorator.

    @Module
    export default class SampleModule extends VuexModule {
      // ...
    }
    
  • The state consists of class properties.

    count = 0;
    
  • Getters are implemented as getter functions.

    get isDefault(): boolean {
      return this.count === 0
    }
    
  • Mutations are methods with the @Mutation decorator. Only one parameter is supported. For more, they should be passed as a single payload object. Methods must be synchronous and shouldn't return a value

    @Mutation
    increment(): void {
      this.count++
    }
    
  • Actions are methods with the @Action decorator. They also support only a single parameter like mutations. They can be asynchronous and should return a promise. The rawError parameter should be set if the method is supposed to throw errors so that those errors aren't wrapped.

    @Action({ rawError: true })
    incrementAsync(): Promise<void> {
      return new Promise<void>((resolve) => {
        setTimeout(() => {
          this.increment()
          resolve()
        }, 100)
      })
    }
    

To use the module from NuxtJS it should be namespaced and dynamic. This can be configured with @Module decorator parameters:

@Module({ name: 'sample', stateFactory: true, namespaced: true })
export default class SampleModule extends VuexModule {
  // ...
}

When using NuxtJS in SSR mode, the suggested pattern in the vuex-module-decorators documentation shouldn't be implemented because it introduces singleton store variables which will cause the state to be shared between requests. Instead, the guidance for SSR in the same documentation should be followed.

The following helper function can make that code a bit simpler:

export function getSampleModule(store: Store<any>): SampleModule {
  return getModule(SampleModule, store);
}

The exported function can then be used instead of the plain getModule function to access the store from elsewhere in the application, e.g. a page or a component. All module members (state, getters, mutations, and actions) are fully typed:

import Vue from 'vue';
import Component from 'vue-class-component';
import { getSampleModule } from '~/store';

@Component
export default class IndexPage extends Vue {
  get count(): number {
    return getSampleModule(this.$store).count;
  }

  get isDefault(): boolean {
    return getSampleModule(this.$store).isDefault;
  }

  increment() {
    getSampleModule(this.$store).increment();
  }

  incrementAsync() {
    getSampleModule(this.$store).incrementAsync();
  }
}

A full working example is in my GitHub repository.

The vuex-module-decorators package is a great choice when using TypeScript as it allows strongly-typed access to the Vuex store with minimum additional plumbing code. When defined and used correctly, such modules can also be used with NuxtJS, even in SSR mode.

Get notified when a new blog post is published (usually every Friday):

Copyright
Creative Commons License