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:
<script src="/path/to/vue.js"></script>
<script src="/path/to/vuex.js"></script>
Using NPM:
npm install vuex --save
Using Yarn:
yarn add vuex
When using a module system (like webpack in vue-cli), you need to explicitly install Vuex via Vue.use()
:
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
Basic Vuex Structure
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.
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.
mutations: {
increment (state) {
state.count++
}
Mutations cannot be called directly, so they are executed with store.commit
:
store.commit('increment');
Mutations can also receive data as a second parameter, which can be a number, string, array, etc.
mutations: {
increment (state, amount) {
state.count += amount
}
}
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:
actions: {
asyncIncrement (context) {
return new Promise((resolve, reject) => {
fetch('someApiX').then(() => {
context.commit('increment')
resolve()
})
})
}
}
or using async/await
:
actions: {
async asyncIncrement (context) {
await fetch('someApiX')
context.commit('increment')
}
}
and use it like this:
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:
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:
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:
getters: {
taskById: (state) => (id) => {
return state.tasks.find(task => task.id === id)
}
}
And use it like so:
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.