Skip to content

Vuex - Centralized State Management in Vue.js

Caution

This article is aimed at Vue 2. Vue 3 is now available and is the recommended way to develop applications with Vue.

Common solutions for sending and receiving data between pages include passing data via URL, cookies, and LocalStorage. While functional, these methods do not provide a standard for data manipulation and are not reactive data sources that harmonize with Vue.js reactivity.

Evan You, creator of Vue.js, developed a tool called Vuex specifically designed for Vue that allows centralized state management in web applications built with this framework. Although not the only data management tool, it integrates best with the Vue ecosystem.

Vuex is a state management pattern library for Vue.js applications. It provides a centralized store for all components in the application, with rules ensuring that state can only be mutated in a predictable fashion.

Using Vuex is especially recommended for medium to large applications that require centralized, reactive data accessible across different components.

Installation

Via CDN, placing it after vue:

html
<script src="/path/to/vue.js"></script>
<script src="/path/to/vuex.js"></script>

Using NPM:

javascript
npm install vuex --save

Using Yarn:

javascript
yarn add vuex

When using a module system (like webpack in vue-cli), you need to explicitly install Vuex via Vue.use():

javascript
import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

Basic Vuex Structure

javascript
const store = new Vuex.Store({
  state: {},
  mutations: {}, 
  actions: {},
  getters: {},
});

state

The state is the object that contains all the data we can access from the different components. Its contents should only be modified through mutations so that its modification is transparent and auditable.

javascript
state: {
  count: 0
}

mutations

The only way to change state in a Vuex store is by committing a mutation. These functions perform the modifications and receive the state as the first parameter.

javascript
mutations: {
  increment (state) {
    state.count++  
  }

Mutations cannot be called directly, so they are executed with store.commit:

javascript
store.commit('increment');

Mutations can also receive data as a second parameter, which can be a number, string, array, etc.

javascript
mutations: {  
  increment (state, amount) {
    state.count += amount
  }
}
javascript
store.commit('increment', 10);

An important point is that mutations are synchronous, meaning any state changes must be done at once and not through asynchronous transactions like database or API queries. For asynchronous state changes, actions are used.

actions

actions are similar to mutations except for two differences:

  • They are dispatched with store.dispatch
  • Instead of mutating state, actions commit mutations
  • actions can contain asynchronous code
  • They receive a context object as the first argument that gives us access to state, mutations, actions and getters
  • actions can return a promise once resolved

Suppose we wanted to increment the counter after querying some API, we could do it like this:

javascript
actions: {
  asyncIncrement (context) {
    return new Promise((resolve, reject) => {
      fetch('someApiX').then(() => {
        context.commit('increment')
        resolve()
      }) 
    })
  }
}

or using async/await:

javascript
actions: {
  async asyncIncrement (context) {
    await fetch('someApiX')
    context.commit('increment') 
  }
}

and use it like this:

javascript
store
  .dispatch('asyncIncrement')
  .then(() => console.log('Counter incremented!'));

Learn more about JavaScript Promises.

getters

getters are used to retrieve processed information from the state. They receive the state as the first argument.

Assuming we had a list of tasks in the state, we could create a getter that returns only the completed tasks:

javascript
state: {
  tasks: [
    { id: 1, text: 'lorem ipsum', done: true },
    { id: 2, text: 'lorem ipsum', done: true },
    { id: 3, text: 'lorem ipsum', done: false },  
    { id: 4, text: 'lorem ipsum', done: false },
    { id: 5, text: 'lorem ipsum', done: true },
  ]
},
getters: {
  doneTasks (state) {
    return state.tasks.filter(task => task.done)
  }  
}

and use it like this:

javascript
store.getters.doneTasks;
/*
[
  { id: 1, text: 'lorem ipsum', done: true }, 
  { id: 2, text: 'lorem ipsum', done: true },
  { id: 5, text: 'lorem ipsum', done: true },  
]
*/

getters can also receive data by returning a function. So we could retrieve a specific task by id through a getter like this:

javascript
getters: {
  taskById: (state) => (id) => {
    return state.tasks.find(task => task.id === id)
  }
}

And use it like so:

javascript
store.getters.taskById(2); // { id: 2, text: 'lorem ipsum', done: true }

This way we can manage data accessible in our components which is mutable through mutations and actions and which we can query in processed form through getters. Giving our application a standard data usage pattern that allows us to scale more quickly.