Vue.js State Managment with Vuex

Doğukan HAN
6 min readDec 7, 2021

--

Introduction

One of the essential aspects of Vue.js development is state management. There are not many state managers exists in Vue World. Vuex is an officially supported state manager for Vue.js applications and, you have to understand deeply Vuex before creating a vue.js application. Good state management makes the application more dynamic and more durable. Bad state management makes applications slow down, creates hard to debug software, decreases code quality.

Vuex provides six different concepts when we start to create our state mechanism. These are:

  • Store: The container of state mechanism, everything is inside this object.
  • State: What we store which types we will use.
  • Getters: How we get from the store,
  • Mutations: How we change the store.
  • Actions: How we change the store.
  • Modules: How we can split different stores.

Note that: Mutations and Actions are very similar but, they have one key difference that I will explain below.

As the image shows, when you update the state, Vue automatically updates relevant views for you. You do not need to change the view or insert any Html elements(as we did in jquery in the past decade). You only need to change the state with actions. If you can not design your state mechanism, the Vuex refreshes unrelated views that slows your application.

Simple Vuex Store can be defined as below.

const store = new Vuex.Store({
state: {

},
getters: {

},
mutations: {

},
actions: {

},
})

State

The state is the place where we store and define what our application needs. Before starting to explain the state, I would like to explain these two concepts:

  • Single state tree:
  • Single source of truth

We want to make sure any information which can change the application’s state is in a single source. The source is called “Single Source of Truth”; Vuex uses a single state tree structure for storing the application’s state. That’s why we only need one “single state tree” per application. You can visit the documentary website for detailed information. For now, it’s good to know just one thing “Store the variable in the store as long as it changes the state of the application.”;

When we want to store personal information(person array) in the application, the store evolves to this:

const store = new Vuex.Store({
state: {
people: []
},
getters: {

},
mutations: {

},
actions: {

},
})

We can also add some initial data:

const store = new Vuex.Store({
state: {
people: [
{ name: 'Kate', age: 30 },
{ name: 'Jack', age: 34 }, { name: 'John', age: 53 },
]
},
getters: {
},
mutations:
},
actions: {
},
})

Getters

After creating the state, we have to implement methods to get the information from the state. Getter methods are defined in the getter section of the store object. We can create method style or property style access methods. The first parameter of the getter is always state object.

getters: {
people(state){ // Shorthand property access.
return state.people;
},
firstPerson(state){ // Shorthand method access
return () => state.people[0];
},
lastPerson : function(state){ // Not shorthand, method access
return () => state.people[state.people.length - 1];
},
personByName(state){ // Shorthand method access with a field.
return (name) => state.people.filter(p=>p.name==name);
}
},

Different ways of defining a getter method exist. You may prefer shorthand defining which is easy to read and write. You can write getter methods with property access style or method access style. Prefer to use the property access style because it’s cached. You can not use property access when you want to pass a variable to the getter method.

this.$store.getters.people; // Property access without brackets
this.$store.getters.firstPerson(); // Function access
this.$store.getters.lastPerson(); // Function access
this.$store.getters.personByName(name); // Function ac. with params

Whenever you want to access getter from a component, you have to define methods in it.

Example of definations:

export default {
name: 'People',
components: {
},
methods: {
getPeople(){
return this.$store.getters.people;
},
getFirstPerson(){
return this.$store.getters.firstPerson();
},
getLastPerson(){
return this.$store.getters.lastPerson();
},
getPersonByName(name){
return this.$store.getters.personByName(name);
},
}
}

After that, you can call the defined methods in your template.

People: {{ getPeople() }} <br>
First: {{ getFirstPerson() }} <br>
Last: {{ getLastPerson() }} <br>
ByName: {{ getPersonByName("Jack") }} <br>

The mapGetters

You can create component methods by using mapGetters helper. The helper easily creates every method for the component without any additional definition. You can pass an array or an object to the `mapGetters` function. The difference is you can specify a different name when you pass an object.

export default {
name: 'People',
components: {
},
computed: {
...mapGetters([
'people',
'firstPerson',
'lastPerson',
'personByName'
]),
}
}

Note that, there are also mapState, mapMutations, mapActions functions exist in the vuex package. They can be used as like mapGetters usage.

Mutations

After specifying what we store and how we access it, it’s time to change the state. Mutations are the only way to change the state. Actions also change the state but, they have to call relevant mutation. The difference between Mutations and Actions is that mutations are synchronous, actions are asynchronous.

  • Use Mutations when the user clicks a button or types a text.
  • Use Actions when you call an API to get resources or when you write a set-timeout function.

You can define mutations in the mutations section in the store object. You may add additional arguments if you need them.

mutations: {
removeAll(state){
state.people = [];
},
addPerson(state,person){
state.people.push(person);
},
changeAgeByName(state, personUpdate){
state.people.forEach(element => {
if(element.name == personUpdate.name){
element.age = personUpdate.newAge
}
});
}
},

The store has a function named commit, which is the way of calling mutations. The first argument is the name of the mutation you want to call. The second argument is an additional argument if you need to pass.

this.$store.commit('addPerson', person);       this.$store.commit('removeAll');
this.$store.commit("changeAgeByName", { name : "Jack", newAge : 5});

There is also another way of calling a mutation is called Object-Style commit. You have to pass the mutation name to the type parameter in the object.

this.$store.commit(
{ type : "changeAgeByName", // Mutation name
name : "Jack", // additional arguments
newAge : 5}); // addition
al arguments.

Actions

In the same way, we can define actions in the actions section of the store object. One of the important things is, you have to call commit with relevant mutation when writing an action.

actions: {
removeAll (context) {
context.commit('removeAll')
},
addPerson(context, person){
context.commit('addPerson', person)
}
},

You can call actions with dispatch method of the store.

this.$store.dispatch('removeAll');
this.$store.dispatch('addPerson', person);

By the way, we don’t need to write actions for calling mutations. We only need them when we work with asynchronous operations. So the examples are just showing the syntax. As the example below shows, you have to use Actions when you call setTimeout or an API.

actions: {
removeAll (context) {
setTimeout(()=>{
context.commit('removeAll')
},1000);
},
getPerson(context){
axios.get('https://someurl/person')
.then(response => {
let person = response.data
context.commit('addPerson', person)
})
}
},

There is also an argument-destructuring syntax that helps to simplify the code. You can directly call the commit without using context to access it.

actions: {
removeAll ({commit}) {
commit('removeAll')
},
addPerson({ commit }, person){
commit('addPerson', person)
}
},

Modules

In software design, it’s good to create pieces(modules) for creating a better structure. Each piece has its responsibility for its job and has no access to the part that is not its job to access. That’s why we use modules in a Vuex application. Store object has modules property which can take zero to many different store objects.

modules: {
moduleA,
moduleB
},

Whenever you define a module, you combine them to a single store object. That means you can call commit(‘myMutation’) if any of your store objects contain this mutation. In most cases, that is not what we want. We want our store object to be in a structure. When we set the namespace property to true, we have to add the namespace to the call. That makes our design to be better.

namespaced: true,
state: {
people : [
{ name: 'Kate', age: 30 },
{ name: 'Jack', age: 34 },
{ name: 'John', age: 53 },
]
},
... other properties.

If the store object is namespaces that our calls must contains the namespace. The last part of the type argument is the actual type and, the before that contains namespace information split by a slash(/). Multiple namespaces supported. If you want to use any of mapGetters, mapState, mapMutations, mapActions functions with a namespace you have to spesify it.

this.$store.dispatch('person/addPerson', person); // person namespace, actions is addPersonthis.$store.dispatch('person/seller/addPerson', person) // person has seller module that contains addPerson actionmapGetters('person',[  // mapGetter with a namespace
'people',
'firstPerson',
'lastPerson',
'personByName'
]),

Resouces

https://vueschool.io

--

--