Managing Vue.js State with Vuex

Joshua Bemenderfer

If you’ve done any serious development of large-scale single page apps, you’re probably familiar with the concept of state management, particularly the Flux architecture, popularized by Facebook through Redux. Here we’ll show you how to implement such a pattern in your Vue application using Vuex.

Preparation

Installation

Vuex, while being an official package for Vue, is not built-in. You have to decide whether or not you’ll need it in your app, then install it accordingly through yarn or npm.

# Yarn
$ yarn add vuex
# NPM
$ npm install vuex --save

Then, in your app bootstrap, make sure you enable the Vuex plugin.

main.js

import Vue from 'vue';
import Vuex from 'vuex';
import App from 'App.vue';

Vue.use(Vuex);

new Vue({
  el: '#app',
  render: h => h(App)
});

Creating a Store

To be able to take advantage of the features Vuex provides, you’ll first need to create a store. A store is essentially a global reactive object which follows the normal Vue reactivity patterns. It cannot be accessed or modified directly in order to ensure a consistent state and allow easy tracking of changes. Here’s how you create one.

store.js

export const store = new Vuex.Store({
  state: {
    safelyStoredNumber: 0
  }
});

Now, to access the store you’ll either have to import it in all of your components, or you can inject it into the root Vue instance to have it automatically injected into every other component in your app as this.$store. We’ll be using the latter method for the rest of this article.

main.js

import Vue from 'vue';
import Vuex from 'vuex';
import App from 'App.vue';
import { store } from './store.js';

Vue.use(Vuex);

new Vue({
  store,
  el: '#app',
  render: h => h(App)
});

Accessing State

Right now you can’t really do anything with the store. It’s an isolated black-box for your state, preventing any unexpected actions to read from or manipulate it. To read data from a store, you’ll need to create a getter.

Using Getters

A getter is simply a function in your store that takes a state object and returns a value from it. In your components, it can be accessed through this.$store.getters.property as a computed property, not a function. If a getter needs a parameter, it can return a second function which takes a parameter.

store.js

export const store = new Vuex.Store({
  state: {
    safelyStoredNumber: 0
  },
  getters: {
    safelyStoredNumber: state => state.safelyStoredNumber,
    storedNumberMatches(state) {
      return matchNumber => {
          return state.safelyStoredNumber === matchNumber;
      }
    }
    // Shorthand:
    // storedNumberMatches: state => matchNumber => state.safelyStoredNumbers === matchNumber
  }
});

The easy way to access getters in your component, however, is through Vuex’s mapGetters helper method. This allows you to mount getters to top-level computed properties in your component.

mapGetters can take an object if you wish to rename the getters in your component.

App.vue

<template>
  <p>The safely stored number: {{safelyStoredNumber}}<p>
</template>

<script>
import { mapGetters } from 'vuex'

export default {
  computed: {
    ...mapGetters([
      // Mounts the "safelyStoredNumber" getter to the scope of your component.
      'safelyStoredNumber'
    ])
  }
}
</script>

Modifying State

Synchronous Mutations

Direct modification of state in Vuex is done by calling a function called a mutation. A mutation is passed the current state and an optional payload. The payload can be any object. Mutations must be synchronous and shouldn’t return a value. They can be used directly by running this.$store.commit(‘mutationName’, payload).

store.js

export const store = new Vuex.Store({
  state: {
    safelyStoredNumber: 0
  },
  ...
  mutations: {
    incrementStoredNumber(state) {
      state.safelyStoredNumber++;
    },
    setStoredNumber(state, newNumber) {
      // newNumber is the payload passed in.
      state.safelyStoredNumber = newNumber;
    }
  }
});

As with getters, Vuex has a convenience method for mutations in component as well, the mapMutations helper method. This allows you to mount mutations as methods in your component.

App.vue

<template>
  <p>The safely stored number: {{safelyStoredNumber}}<p>
</template>

<script>
import { mapMutations } from 'vuex'

export default {
  ...
  methods: {
    ...mapMutations([
      // Mounts the "incrementStoredNumber" mutation to `this.incrementStoredNumber()`.
      'incrementStoredNumber',
      // Mounts the "setStoredNumber" mutation to `this.setStoredNumber(newNumber)`.
      'setStoredNumber'
    ])
  }
}
</script>

Asynchronous Actions

In more complicated apps, it’s likely that you will need to perform some asynchronous actions that modify the state. Vuex handles this with actions. They are defined on your state object as well, and are passed the entire state context, which allows them to access getters and commit mutations. They are expected (but not required) to return a promise indicating completion status. Using ES2017 async/await, you can write very terse but easy to understand async actions. Actions are used in components directly with this.$store.dispatch(‘actionName’, payload).then(response => {}).

To modify state within an action, use context.commit(‘mutationName’, payload). Multiple mutations are allowed inside an action.

store.js

import myRemoteService from './my-remote-service.js'

export const store = new Vuex.Store({
  state: {
    safelyStoredNumber: 0
  },
  ...
  actions: {
    async setNumberToRemoteValue(context) {
      // Commits the 'setStoredNumber' mutation with the value of whatever myRemoteService.getRemoteValue() resolves through a promise.
      context.commit('setStoredNumber', await myRemoteService.getRemoteValue());
      return Promise.resolve();
    },
  }
});

If you're not familiar with async / await, seriously, go read about it. It's awesome. The short of it is, it pauses the execution of the current function until the awaited promise resolves, allowing you to essentially use promise resolutions as variables without all the extra boilerplate normally needed.

The Vuex convenience method for actions, (predictably named mapActions) is used in the same way as the one for mutations.

App.vue

<template>
  <p>The safely stored number: {{safelyStoredNumber}}<p>
</template>

<script>
import { mapActions } from 'vuex'

export default {
  ...
  methods: {
    ...mapActions([
      // Mounts the "setNumberToRemoteValue" action to `this.setNumberToRemoteValue()`.
      'setNumberToRemoteValue',
    ])
  }
}
</script>

Modularizing

A single store is fine if you’re only working with a small set of data, but inevitably at some point you’ll want to split your constantly-growing list of actions, mutations, and getters into separate sections. Thankfully Vuex provides a system to do this as well. Modules. Despite the scary name, a module is simply a normal object with state, getters, mutations, and actions properties. You can easily create one by doing this:

my-store-module.js

export const myModule = {
  // This makes your getters, mutations, and actions accessed by, eg: 'myModule/myModularizedNumber' instead of mounting getters, mutations, and actions to the root namespace.
  namespaced: true,
  state: {
    myModularizedNumber: 0
  },
  getters: {
    myModularizedNumber: state => state.myModularizedNumber
  },
  mutations: {
    setModularizedNumber(state, newNumber) {
      state.myModularizedNumber = newNumber
    }
  }
}

store.js

import { myModule } from './my-store-module.js';

export const store = new Vuex.Store({
  modules: {
  	myModule
  },
  state: {
    safelyStoredNumber: 0
  },
  ...
});

You can nest modules in modules, as far down as you’d like. Additionally, mapGetters, mapMutations, and mapActions can all take a first argument, which is a module namespace, in order to keep you from having to write code like this:

...mapGetters([
  'myModule/nestedModule/subNestedModule/exampleGetter',
  'myModule/nestedModule/subNestedModule/anotherGetter',
])

You can instead write it like this:

...mapGetters('myModule/testedModule/subNestedModule', [
  'exampleGetter',
  'anotherGetter'
])

Hope that helps clear up state management with Vuex for you!

✖ Clear

🕵 Search Results

🔎 Searching...