Introduction

Vanilla, strongly-typed store accessor.

The accessor serves two purposes:

  • It wraps the store so that it can be typed without conflicting with the default types for $store in a Nuxt project.

  • It allows us to avoid creating impossible type definitions for namespaced magic strings, like commit('mysubmodule/mutation').

Structure

  1. Getters, state, mutations and actions are flattened.

  2. Getters take priority over state (so state is not included if a getter of the same name exists).

  3. Modules are namespaced.

Because the accessor is flattened, you should avoid using the same name more than once between your getters, state, mutations and actions or you may receive the following error: Cannot set property <name> of #<Object> which has only a getter.

So, for example:

// Accesses this.$store.state.email
this.$accessor.email

// Accesses this.$store.getters['fullName']
this.$accessor.fullName

// Runs this.$store.dispatch('initialiseStore')
this.$accessor.initialiseStore()

// Runs this.$store.commit('SET_NAME', 'John Doe')
this.$accessor.SET_NAME('John Doe')

// Accesses this.$store.state.submodule.id
this.$accessor.submodule.id

// etc.

Typing the accessor

Adding types is simple. A helper function, getAccessorType, is provided, which compiles to nothing and only serves to return the correct type of the accessor so that it can be used where you see fit.

Make sure you define types correctly following these instructions.

Using the accessor

Components, fetch and asyncData

components/sampleComponent.vue
import Vue from 'vue'

export default Vue.extend({
  fetch({ app: { $accessor } }) {
    $accessor.fetchItems()
  },
  asyncData({ app: { $accessor } }) {
    return {
      myEmail: $accessor.email,
    }
  },
  computed: {
    email() {
      // This (behind the scenes) returns this.$store.getters['email']
      return this.$accessor.email
    },
  },
  methods: {
    resetEmail() {
      // Executes this.$store.dispatch('submodule/resetEmail', 'new@email.com')
      this.$accessor.submodule.resetEmail('new@email.com')
    },
  },
})

Composition API

components/sampleComponent.vue
import { defineComponent, computed } from '@nuxtjs/composition-api'

export default defineComponent({
  setup(_props, { root }) {
    const counter = computed(() => root.$accessor.counter)

    return {
      counter,
    }
  },
})

Since you are already using Composition API, you might as well create a hook dedicated to this

hooks/useAccessor.ts
import { wrapProperty } from '@nuxtjs/composition-api'

export const useAccessor = wrapProperty('$accessor', false)

Should your IDE not properly recognize type declarations setup as described in these instructions, you may want to explicitly define accessor types like in the following example:

hooks/useAccessor.ts
import { wrapProperty } from '@nuxtjs/composition-api'
import { accessorType } from '~/store'

export const useAccessor = (): typeof accessorType =>
  wrapProperty('$accessor', false)()

Middleware

middleware/test.ts
import { Context } from '@nuxt/types'

export default ({ redirect, app: { $accessor } }: Context) => {
  // You can access the store here
  if ($accessor.email) return redirect('/')
}

Mapping properties into your component

typed-vuex also exports a convenience helper function to allow you to easily map the accessor into your component.

components/sampleComponent.vue
import Vue from 'vue'

// Nuxt
import { accessorType } from '~/store'
const mapper = createMapper(accessorType)
// Vue
import { accessor } from './src/store'
const mapper = createMapper(accessor)

export default Vue.extend({
  computed: {
    // Direct mapping to a property
    ...mapper('name'),
    // or array of properties
    ...mapper(['name', 'email']),
    // or submodules
    ...mapper('submodule', ['name', 'email']),
  },
  methods: {
    // All your methods should be included in the methods object,
    // whether they are mutations, actions (or even a method returned
    // by a getter or stored in state)
    ...mapper('resetName'),
    resetEmail() {
      // You can access directly off your component instance
      console.log(this.name, this.email)
      this.resetName()
    },
  },
})