Adding Actions to a Vuex Store

John Au-Yeung - Jan 26 '21 - - Dev Community

Check out my books on Amazon at https://www.amazon.com/John-Au-Yeung/e/B08FT5NT62

Subscribe to my email list now at http://jauyeung.net/subscribe/

Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.

With Vuex, we can store our Vue app’s state in a central location.

In this article, we’ll look at how to add actions to our Vuex store to change the store’s state.

What are Actions?

Actions are similar to mutations, but they commit mutations instead of mutating the state. Actions can also commit asynchronous operations unlike mutations.

For example, we can add a simple action as follows:

const store = new Vuex.Store({  
  state: {  
    count: 0  
  },  
  mutations: {  
    increase(state) {  
      state.count++;  
    }  
  },  
  actions: {  
    increase(context) {  
      context.commit("increase");  
    }  
  }  
});
Enter fullscreen mode Exit fullscreen mode

An action takes a context object, which we can use to call commit to commit a mutation.

Dispatching Actions

We can dispatch actions by calling store.dispatch('increase') .

It’s much more useful than commit mutations directly because we can run asynchronous operations with it.

For example, we can dispatch an action as follows in Vue app:

index.js :

const store = new Vuex.Store({  
  state: {  
    count: 0  
  },  
  mutations: {  
    increase(state, payload) {  
      state.count += payload.amount;  
    }  
  },  
  actions: {  
    increaseAsync({ commit }, payload) {  
      setTimeout(() => {  
        commit("increase", payload);  
      }, 1000);  
    }  
  }  
});

new Vue({  
  el: "#app",  
  store,  
  methods: {  
    ...Vuex.mapActions(["increaseAsync"])  
  },  
  computed: {  
    ...Vuex.mapState(["count"])  
  }  
});
Enter fullscreen mode Exit fullscreen mode

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://unpkg.com/vue/dist/vue.js"></script>  
    <script src=[https://unpkg.com/vuex"></script>  
  </head>  
  <body>  
    <div id="app">  
      <button @click="increaseAsync({amount: 10})">Increase</button>  
      <p>{{count}}</p>  
    </div>  
    <script src="index.js"></script>  
  </body>  
</html>
Enter fullscreen mode Exit fullscreen mode

In the code above, we have the action increaseAsync with the following code:

increaseAsync({ commit }, payload) {  
  setTimeout(() => {  
    commit("increase", payload);  
  }, 1000);  
}
Enter fullscreen mode Exit fullscreen mode

In the action method, we commit the increase mutation with a payload object that we pass in when we dispatch the action.

In the Vue instance, we have:

methods: {  
    ...Vuex.mapActions(["increaseAsync"])  
},  
computed: {  
  ...Vuex.mapState(["count"])  
}
Enter fullscreen mode Exit fullscreen mode

which maps the actions in the store by calling mapActions , and added computed properties by mapping them from the store by using mapState so we get a count property which is computed from state.count as a computed property.

Then in the template, we call increaseAsync when we press the Increase button to update state.count after 1 second.

Finally, we should see the number updated since we mapped the state from the store.

Composing Actions

We can chain actions that return promises.

For example, we can create an action to update 2 counts as follows:

index.js :

const store = new Vuex.Store({  
  state: {  
    count1: 0,  
    count2: 0  
  },  
  mutations: {  
    increase(state, payload) {  
      state.count1 += payload.amount;  
    },  
    decrease(state, payload) {  
      state.count2 -= payload.amount;  
    }  
  },  
  actions: {  
    async increaseAsync({ commit }, payload) {  
      return Promise.resolve(commit("increase", payload));  
    },  
    async decreaseAsync({ commit }, payload) {  
      return Promise.resolve(commit("decrease", payload));  
    },  
    async updateCounts({ dispatch }, payload) {  
      await dispatch("increaseAsync", payload);  
      await dispatch("decreaseAsync", payload);  
    }  
  }  
});

new Vue({  
  el: "#app",  
  store,  
  methods: {  
    ...Vuex.mapActions(["updateCounts"])  
  },  
  computed: {  
    ...Vuex.mapState(["count1", "count2"])  
  }  
});
Enter fullscreen mode Exit fullscreen mode

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://unpkg.com/vue/dist/vue.js"></script>  
    <script src="https://unpkg.com/vuex"></script>  
  </head>  
  <body>  
    <div id="app">  
      <button @click="updateCounts({amount: 10})">Update Counts</button>  
      <p>Count 1: {{count1}}</p>  
      <p>Count 2: {{count2}}</p>  
    </div>  
    <script src="index.js"></script>  
  </body>  
</html>
Enter fullscreen mode Exit fullscreen mode

In the code above, we have the updateCounts action switch calls dispatch with the increaseAsync action and payload . Then it calls dispatch with the decreaseAsync action and payload .

increaseAsync commits the increase mutation, and decreaseAsync commis the decrease mutation.

Since they all have Promise.resolve , they’re all async.

Then we include the updateCounts action from the store in our Vue instance with mapActions . And we also include the count1 and count2 states with mapState .

Then when we click the Update Counts button, we call updateCounts , and then count1 and count2 are updated as we click the button. count1 should increase by 10 each time and count2 should decrease by 10 each time we click it.

Conclusion

We can use actions to commit one or more mutations or dispatch other actions.

It’s handy for grouping store operations together and running asynchronous code since mutations are always synchronous.

We can use mapActions to include them in our components.

Actions are dispatched by calling dispatch , while mutations are committed with the commit method.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .