DEV Community

Cover image for Vuex With Class Components
Dhruva Srinivas
Dhruva Srinivas

Posted on • Originally published at carrotfarmer.github.io

Vuex With Class Components

Helloooo, in this post I'll show you how you can use vuex with TypeScript and class-components.

Disclaimer

In this tutorial I will be using:

  • Vue 2
  • Vuex ^3.6.2
  • TypeScript 4.5

What we're gonna build

Image description

Creating the project

Now let's start coding! First we have to create our Vue.js app. To do that run:

vue create vuex-counter
Enter fullscreen mode Exit fullscreen mode

and make sure you include Vuex, TypeScript and Use class components in your options.

Creating the store

Let's now create the Vuex store. The store will consist of a singular state which will contain the main count from where we'll derive the incremented and decremented ones.

src/store/index.ts

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    count: 1,
  },
  getters: {},
  mutations: {},
  actions: {},
  modules: {},
});
Enter fullscreen mode Exit fullscreen mode

Getters

Using the count variable in the state we will use getters to fetch the current count, the incremented count and the decremented count. Before we do that though, we'll first create a type for our state so that
we can explicitly type out the arguments required for our getters.

src/types.ts

export interface StateType {
  count: number;
}
Enter fullscreen mode Exit fullscreen mode

src/store/index.ts
Now we can use this type to create our getters.

import Vue from "vue";
import Vuex from "vuex";
import { StateType } from "@/types";

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    count: 1,
  },
  getters: {
    currentCount(state: StateType): number {
      return state.count;
    },
    previousCount(state: StateType): number {
      return state.count - 1;
    },
    nextCount(state: StateType): number {
      return state.count + 1;
    },
  },
  mutations: {},
  actions: {},
  modules: {},
});
Enter fullscreen mode Exit fullscreen mode

Mutations and Actions

Now let's create some simple mutations to mutate the count variable of the state. This will cause
nextCount and previousCount to update accordingly.

src/store/index.ts

import Vue from "vue";
import Vuex from "vuex";
import { StateType } from "@/types";

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    count: 1,
  },
  getters: {
    currentCount(state: StateType): number {
      return state.count;
    },
    previousCount(state: StateType): number {
      return state.count - 1;
    },
    nextCount(state: StateType): number {
      return state.count + 1;
    },
  },
  mutations: {
    increment(state: StateType): void {
      state.count++;
    },
    decrement(state: StateType): void {
      state.count--;
    },
  },
  actions: {},
  modules: {},
});
Enter fullscreen mode Exit fullscreen mode

Here we are returning void because apart from mutating the count value we are not returning anything.
Of course, now we need to run these mutations so lets create some actions for that.

src/store/index.ts

import Vue from "vue";
import Vuex, { ActionContext } from "vuex";
import { StateType } from "@/types";

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    count: 1,
  },
  getters: {
    currentCount(state: StateType): number {
      return state.count;
    },
    previousCount(state: StateType): number {
      return state.count - 1;
    },
    nextCount(state: StateType): number {
      return state.count + 1;
    },
  },
  mutations: {
    increment(state: StateType): void {
      state.count++;
    },
    decrement(state: StateType): void {
      state.count--;
    },
  },
  actions: {
    increment(ctx: ActionContext<StateType, StateType>): void {
      ctx.commit("increment");
    },
    decrement(ctx: ActionContext<StateType, StateType>): void {
      ctx.commit("decrement");
    },
  },
  modules: {},
});
Enter fullscreen mode Exit fullscreen mode

Alrighty, now we're done with the store and we can move onto using these little bits of state in our UI!

Using the store in our component

I have a created a component called Counter and set it up like this:

<template>
  <div>
    <h1>vue counter</h1>
    <span>
      <button>&lt; 0</button>
      1
      <button>&gt; 2</button>
    </span>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from "vue-property-decorator";

@Component
export default class Counter extends Vue {}
</script>

<style scoped>
h3 {
  margin: 40px 0 0;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  display: inline-block;
  margin: 0 10px;
}
a {
  color: #42b983;
}
</style>
Enter fullscreen mode Exit fullscreen mode

Now normally to access our store we would so something like:

this.$store.count; // etc..
Enter fullscreen mode Exit fullscreen mode

But Vuex's TypeScript support is kinda jank and it doesn't work well with class components. So we will have to add a library called vuex-class to use our store in our component.

yarn add vuex-class
Enter fullscreen mode Exit fullscreen mode

or

npm install vuex-class
Enter fullscreen mode Exit fullscreen mode

So the way vuex-class works is you have an associated decorator for a getter, mutation etc. and we pass
that decorator to a variable with the same name as the name of the mutation or getter in the store. For example the way we would call our currentCount getter is:

src/components/Counter.vue

<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import { Getter } from "vuex-class";

@Component
export default class Counter extends Vue {
  // getters
  @Getter currentCount!: number;
}
</script>
Enter fullscreen mode Exit fullscreen mode

And we can call this currentCount property in our template.

src/components/Counter.vue

<template>
  <div>
    <h1>vue counter</h1>
    <span>
      <button>&lt; 0</button>
      {{ currentCount }}
      <button>&gt; 2</button>
    </span>
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

Now we can do the same for the other getters:

src/components/Counter.vue

<template>
  <div>
    <h1>vue counter</h1>
    <span>
      <button>&lt; {{ previousCount }}</button>
      {{ currentCount }}
      <button>&gt; {{ nextCount }}</button>
    </span>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import { Getter } from "vuex-class";

@Component
export default class Counter extends Vue {
  // getters
  @Getter currentCount!: number;
  @Getter previousCount!: number;
  @Getter nextCount!: number;
}
</script>
Enter fullscreen mode Exit fullscreen mode

We can use the same syntax to include our actions using @Action. Then we will be able to use it as
the buttons' @click handlers.

src/components/Counter.vue

<template>
  <div>
    <h1>vue counter</h1>
    <span>
      <button @click="decrement">&lt; {{ previousCount }}</button>
      {{ currentCount }}
      <button @click="increment">&gt; {{ nextCount }}</button>
    </span>
  </div>
</template>

<script lang="ts">
import { StateType } from "@/types";
import { Component, Vue } from "vue-property-decorator";
import { ActionContext } from "vuex";
import { Getter, Action } from "vuex-class";

@Component
export default class Counter extends Vue {
  // getters
  @Getter currentCount!: number;
  @Getter previousCount!: number;
  @Getter nextCount!: number;

  // actions
  @Action increment!: ActionContext<StateType, StateType>;
  @Action decrement!: ActionContext<StateType, StateType>;
}
</script>
Enter fullscreen mode Exit fullscreen mode

And that's it! You can use the same procedure to use them in bigger/more complex stores too! vuex-class also has support for modules and you can use them with namespaces.

I'll catch you guys in my next post!

Discussion (0)