• 15 hours
  • Medium

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 4/13/21

Make Changes to Data in Vuex

Log in or subscribe for free to enjoy all this course has to offer!

What Are Mutations?

In the last chapter, you learned how to define our data store in Vuex with state and getters and retrieve data with methods like  mapState  and  mapGetters. In this chapter, we're going to take the next step with managing our data store: updating and making changes to data in Vuex with mutations: 

As we saw in the initial  store.js  scaffold:

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
},
mutations: {
},
actions: {
}
})

The second key defined in the store is the mutations key. As the name suggests, it contain an object of all of the properties responsible for making changes to the  state.

Defining a Mutation

If you take the traditional counter example, you can define a mutation as:

export default new Vuex.Store({
state: {
count: 0
},
mutations: {
INCREASE_COUNT(state) {
state.count += 1
}
},
actions: {
}
})

The mutation, by default, receives the  state  as the first argument which can then be used to make the necessary changes. In the example above, we are increasing the count of  state.count  by 1.

However, this is limiting if you want to make the amount incremented dynamic. Fortunately, the mutations can take a second argument, which is the payload parameter.

export default new Vuex.Store({
state: {
count: 0
},
mutations: {
INCREASE_COUNT(state, payload) {
state.count += Number(payload)
}
},
actions: {
}
})

In the revised example, the user can now:

  • Pass the amount they want to increment using  state.count.

  • Set a default amount of 1 in case the user forgets to pass an amount.

  • Convert amount to a number so it doesn't append like a string by accident.

Committing a Mutation

When it comes to changing the  state  of an application, it is critical that it only occurs when it is supposed to. In other words, pay attention to these events so you can easily identify any mistakes.

Rather than invoke the mutation like a normal function, use commit.

When committing a mutation, the action takes two parameters:

  • Name of the mutation

  • Payload (optional)

this.$store.commit('INCREMENT_COUNT', 2)

When a mutation is committed, the change takes place immediately. In other words, Vuex mutations are synchronous, which means that you cannot do something like fetch data from an API.

You're probably wondering how one should commit mutations. To answer that question, we need to talk about actions.

What Are Actions?

So far, you've learned that:

  • State contains our data store.

  • Getters serve as the computed properties of our data store.

  • Mutations allow us to update/change the state.

The last piece of the puzzle is actions:

Actions serve as the primary vehicle we will use to coordinate the logic behind mutations. In other words, they are similar to the  methods  property in a Vue instance. 

A comparison showing how the actions in a Vuex store are similar to the method property in a Vue instance.
A comparison showing how the actions in a Vuex store are similar to the method property in a Vue instance.

Defining an Action

Continuing with our counter example, we could define our action as:

export default new Vuex.Store({
state: {
count: 0
},
mutations: {
INCREASE_COUNT(state, amount = 1) {
state.count += Number(amount)
}
},
actions: {
incrementCount(context, amount) {
context.commit('INCREMENT_COUNT', amount)
}
}
})

An action is composed of its name, the context parameter, and an optional payload (just like in mutations). In the example above:

  • The name of the action is  incrementCount.

  • The  context  parameter gives us access to the same methods and properties in the Vuex store instance (commit, state, getters, etc.).

  • The payload we need to pass to the mutation, so it increases correctly is amount.

Why Are Actions Useful?

Based on the example above, you might be wondering why we don't call the mutation directly. What happens when we need to decrement the count by an amount?

export default new Vuex.Store({
state: {
count: 0
},
mutations: {
INCREASE_COUNT(state, amount = 1) {
state.count += Number(amount)
},
DECREASE_COUNT(state, amount = 1) {
state.count -= Number(amount)
}
},
actions: {
incrementCount(context, amount) {
context.commit('INCREMENT_COUNT', amount)
}
}
})

It's apparent that we need some logic to determine when to fire each mutation. To accommodate this, let's genericize our action to the following:

export default new Vuex.Store({
state: {
count: 0
},
mutations: {
INCREASE_COUNT(state, amount = 1) {
state.count += Number(amount)
},
DECREASE_COUNT(state, amount = 1) {
state.count -= Number(amount)
}
},
actions: {
updateCount(context, amount) {
if (amount >= 0) {
context.commit('INCREASE_COUNT', amount)
} else {
context.commit('DECREASE_COUNT', amount)
}
}
}
})

Why don't we just combine the mutations into one?

We could genericize the mutation to something like  CHANGE_COUNT, but it would mean that our history becomes less descriptive and harder to debug. As a result, it is better to keep mutations single-purpose. Leave the logic to actions.

Besides, since the  context  parameter can be used to access many different properties of the Vuex store, a common technique you will see in code is object destructuring. This is done to simplify the code and make it easier to read. Using our example, it would look like this:

export default new Vuex.Store({
state: {
count: 0
},
mutations: {
INCREASE_COUNT(state, amount = 1) {
state.count += Number(amount)
},
DECREASE_COUNT(state, amount = 1) {
state.count -= Number(amount)
}
},
actions: {
updateCount({ commit }, amount) {
if (amount >= 0) {
commit('INCREASE_COUNT', amount)
} else {
commit('DECREASE_COUNT', amount)
}
}
}
})

In addition to providing the freedom to determine the logic of when mutations are fired,  actions are asynchronous. This means that you can make API calls and commit mutations only when it is successful. Or whatever else your heart desires!

Now that you know how to define actions, the next question is: how do you use them in components?

Using Actions in Components

Similar to how there is a special term for invoking mutations (i,e., committing), there is also one for actions: dispatching. In other words, you are sending the action off to perform a task. If you wanted to dispatch an action from your component, it would look something like this:

<template>
<div>
<p>{{ count }}</p>
<button @click="sendUpdateCountAction">Increment</button>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
computed: {
...mapState(['count'])
},
methods: {
sendUpdateCountAction() {
this.$store.dispatch('updateCount')
}
}
}
</script>

However, similar to how you use  mapState  and  mapGetters, there is an equivalent for actions as well:  mapActions.

<template>
<div>
<p>{{ count }}</p>
<button @click="updateCount">Increment</button>
</div>
</template>
<script>
import { mapState, mapActions } from 'vuex'
export default {
computed: {
...mapState(['count'])
},
methods: {
...mapActions(['updateCount'])
}
}
</script>

And with that, you now know all the major pieces of Vuex!

Exercise

You will find the source code for the exercises in the course's GitHub repo in the  cafe-with-a-vue folder. To get started, check out the  P4C4-Begin  branch.

Instructions

  1. Migrate event for adding to shopping to cart from  Home.vue  and  MenuItem.vue  into Vuex

  2. Create a mutation that updates the  shoppingCart  state

  3. Create an action that can be triggered in  MenuItem.vue  that updates the Shopping Cart in  Home.vue

Let's Recap!

In this chapter, you learned about:

  • How to use mutations to update the state.

  • How to use actions to manage your mutations.

  • How to declare and use actions in your components.

Next, we'll summarize what we learned during this part of the course. See you there!

Example of certificate of achievement
Example of certificate of achievement