wejotek-vue-vuex

wejotek + vue.js + wordpress – then vuex

So, to recap, we have defined the rationale of creating this series of geeky blog posts at the beginning. Then we created the project. And more. Which included mixins, axios and the WordPress API. Now, Vuex!

Shortly after completing this amazing feat of getting my WordPress posts to show up in my freshly created Vue.js page – I realized I did something, well, kind of right. But that there may have been another, more betterer way to architect this thing.

The More Betterer Way – Vuex?

As I began to ponder the betterer way to do this, I realized that I needed to manage state. And that I was, in a way, using a mixin to do so. Which isn’t right. My API, currently in a mixin, should actually be wrapped in a state object. The mixin should go away. The point of a mixin is to make common code available to multiple components in an easy way. Which kind of fits the bill, but does it really? Uhm, no. Enter Vuex.

Why is Vuex More Betterer?

First, a quick bit on Vuex. What is it? Well, to quote the source,

Vuex is a state management pattern + library for Vue.js applications.

Vuex themselves

Bottom line, Vuex takes away the complexities of managing state in our application by, well, doing it for us. Brilliant! And on top of that, they handle making sure our state is reactive. More brilliant! And even more betterer, the pattern give you traceability (which isn’t really a word, apparently). You can easily track, or trace as it may be, when and where your state changed. This may be overkill for a small application – maybe like mine – but for large applications it makes a lot of sense.

My Use Case

As mentioned, I decided to use Vuex to manage the sate of my posts. Which sounded simple, and I’m sure it would have been, until I added Typescript to the mix. Let me tell you, there are a billion posts on how to do this. All of them slightly different. Many felt incomplete. Hopefully, I can make this plain enough that someone else, maybe even you, doesn’t have to spend all the time researching that I did…

The Tools

After TOO MUCH trial and error, these are the tools I used to implement a Vuex Store, in a Vue app, using Typescript to keep things “safe”.

The Process

There are a couple ways to load modules using Vuex, statically and dynamically. I heard that all the cool kids loaded modules dynamically – so that is the route I took. “How”, you are asking (don’t ask why). Well, let me show you!

Step 1

First, I created a Store class, in a store folder, at the root the src folder in my app. Yes. I know. This is truly mind boggling.

The class looks like this:

import Vue from 'vue'
import Vuex from 'vuex';
import { IPostsState } from './posts/Posts'

Vue.use(Vuex)

export interface iRootState {
    posts: IPostsState
}
export default new Vuex.Store<iRootState>({})

To add some color here, I imported Vuex and told Vue to use it. I created an interface for the root state of this application. I decided to take advantage of Vuex modules, so I added a Posts module to the root state interface.

The Posts module is defined by another interface, iPostsState. More on that later.

Last, I create our store in a dynamic fashion. Or, more accurately, I create it with nothing in it. So it must be dynamic! This threw me for a 30 minute loop all by itself. More on that later…

Step 2

Next, I created a Posts file, in a posts folder in my newly created store folder, at the root the src folder in my app. Yes. I know. This too is truly mind boggling.

It looks a lot like this:

import { Module, VuexModule, Mutation, Action } from 'vuex-module-decorators'
import store from '../Store'
import WordPress from '../../mixins/WordPress'

export interface IPost {
    title: string,
    excerpt: string
}

export interface IPostsState {
    posts: [IPost],
    loaded: boolean
}

@Module({
    dynamic: true,
    store: store,
    namespaced: true,
    name: "posts"
})
export class Posts extends VuexModule implements IPostsState {
    public posts: [IPost] = <[IPost]>{}
    public loaded: boolean = false

    @Mutation
    updatePosts(posts: [IPost]) {
        this.posts = posts
        this.loaded = true
        // console.log('getPosts:updatePosts', this.loaded)
    }

    @Action
    fetchPosts(): void {
        WordPress.getPosts(5).then(response => {
            // this.updatePosts(response.data)
            let result: [IPost] = response.data.map((row: any) => {
                // console.log('getPosts:row', row)
                return {
                    title: row.title.rendered,
                    excerpt: row.excerpt.rendered
                }
            })
            this.updatePosts(result)
        }).catch(error => {
            console.log('getPosts:error', error)
        }).finally(() => {
            console.log('getPosts:complete')
        })
    }
}

Let us break this monster down a little.

First, I import a slew of code from Vuex Module Decorators. More on these later. I also pull in the store that we had just created and the WordPress API logic we created in our previous post.

Next, I defined a couple of interfaces, one of which we have already used. Keeping it simple, my Post interface simply provides for a title and an excerpt. Then I created the PostState interface we used in our Store class. This simply provides for an array of Posts and a simple boolean letting me know if the posts are loaded or not. Bam! That was easy! Not really… I probably looked at 10 posts just to get to this point…

Now, for some meat. Using decorators, I created the Vuex module. Let’s walk through it.

@Module({
    dynamic: true,
    store: store,
    namespaced: true,
    name: "posts"
})

Using the Module decorator from ‘vuex-module-decorators’, I set things up. First, I proclaimed that the module shall henceforth be dynamic, meaning we can load it on the fly in my mind. I add the store we created in the beginning. This is where I got hung up for a minute. Or more. No judging. You have to give the

The cool thing about Vuex modules is that you can namespace them to keep things in the store nice and tidy. Doing our best to ensure that when we access the store we are accessing what we want to be accessing. Make sense? Regardless, I wanted to namespace this module, hence “namespaced: true”. Brilliant! Lastly, I named my namespace, “posts”. More brilliant! On to the class itself

export class Posts extends VuexModule implements IPostsState {

This is a little crazy if you don’t use Typescript, but like I said earlier, we are keeping this app “safe” here. My Posts class is extending the VuexModule which does a bunch of cool stuff for me. More on that in a few. Then I make my class implement my PostState interface. This allows me to do what I did back in the Store class, making sure that “posts” was safe by using an interface. Super safe!

Now we see the magic of the VuexModule!

public posts!: [IPost]
public loaded: boolean = false

To make things reactive in Vue without Typescript and the VuexModule, you need all kinds of funky code. We are able to bypass that here. To access the Store and make things reactive, all I do is simply create a couple of public properties. In this case, I create my array of IPost and a boolean for loaded. This satisfies the requirements of my IPostsState interface. If I were to have left out either of those properties, Typescript would have given me an earful and the app wouldn’t compile – super safe!

Next, I added and Action using the ‘vuex-module-decorators’ decorator. This guy simply makes an API request using my WordPress object that wraps axios, maps the data to match my IPost interface and then calls my mutator.

Not that a mutator is a thing or anything. In the future, I will liely want persist my posts in localstorage rather than calling my API everytime this Action is called. But for now, this is more betterer enough!

Next, I added a Mutation. Sounds grotesque. But it really wasn’t – thankfully. Because all I had to do was use the @Mutation decorator.

    @Mutation
    updatePosts(posts: [IPost]): void {
        this.posts = posts
        this.loaded = true
        // console.log('getPosts:updatePosts', this.loaded)
    }

Without these decorators, I would have to resort to ugly things like this:

store.commit('updatePosts',{blah, blah, blah})

But here at WeJoTek, we say “NO!” to ugly! Though we recognize beauty is in the eye of the beholder. So, you do you.

Quick note. If you don’t know, you need an Action in Vuex if you are executing a async operation within the Store. Mutations cannot be async. So, I am making my async API call in an Action and passing the results along to the Mutation, which synchronously updates the Store.

And really, that explains the Store stuff. Now, how to implement it in a Component?!?

Step 3

Implementation! Here is where I got hung up… Reading through a couple of posts, I learned that the way to add a dynamic Module to the Vuex store was to call getModule from “vuex-module-decorators”. And the way they did it was like this in the Posts class:

export const PostsModule = getModule(Posts)

But, the only reason one would do this, I found out after many moments of frustration, was if one wanted to use the PostsModule directly in the component. Like, PostsModule.fetchData(). Which is “safe” technically, but it seemed weird. Like I was just pulling objects in my component from thin air and using them. Which is OK if you are into that. But it didn’t feel right.

So, I got rid of that and looked for another solution. Which is where I am at the time of this writing. It looks like this:

<template>
  <div class="home">
    <div v-if="!loaded">loading.</div>
    <div v-else v-for="(post, index) in posts" :key="index">
      <h1 v-html="post.title"></h1>
      <div v-html="post.excerpt"></div>
    </div>
  </div>
</template>

<script lang="ts">
import Vue from "vue";
import Component from "vue-class-component";
import { namespace } from "vuex-class";
import { getModule } from "vuex-module-decorators";

import { IPost, Posts } from "../store/posts/Posts";

getModule(Posts);
const posts = namespace("posts");

@Component
export default class Home extends Vue {
  @posts.State
  loaded!: boolean;

  @posts.State
  posts!: [IPost];

  @posts.Action
  fetchPosts!: () => void;

  created() {
    this.fetchPosts();
  }
}
</script>

Let’s break it down.

import Vue from "vue";
import Component from "vue-class-component";
import { namespace } from "vuex-class";
import { getModule } from "vuex-module-decorators";

import { IPost, Posts } from "../store/posts/Posts";

First, I import everything. The two items we are going to look at are “namespace” and “getModule” Don’t forget “getModule”. NEVER forget “getModule.

“namespace” is cool because we can get and object from the Store by, you guessed it, the namespace itself. Brilliant!

More on “getModule” in a second…

Then I pull in my IPost interface and the Post class itself. This is prety self-explanatory – no?

To our component:

@Component
export default class Home extends Vue {
  @posts.State
  loaded!: boolean;

  @posts.State
  posts!: [IPost];

  @posts.Action
  fetchPosts!: () => void;

  created() {
    this.fetchPosts();
  }
}

This is where the decorators make everything so clean! As you can see, effectively the store becomes part of my class and I can access it with “this”. Super! But now the ugly…

getModule(Posts);
const posts = namespace("posts");

I still think that calling getModule, even here, is kind of funky. But, since we are using dynamic modules in Vuex in a type-safe fashion, it is what it is I guess. I found out the hard way, that unless you do it this way, the Posts modules is never available in the Store and therefore you cannot gain access to it. Hence, getModule. If someone has a better pattern, I’d love to hear about it.

Then we gain access to the Posts store through it’s namespace. using “namespace”. This is what allows us to use the decorators and make everything so pretty!

Step 4

Bask in the glory of a pretty sweet way to implement a store into a Vue app. While there are a few things that feel a little weird about this implementation, overall I am happy with it.

Next step, some style!

Tags: No tags

Comments are closed.