Vuex - Vue State Management - Part III - Mutations

Vuex - Vue State Management - Part III - Mutations

Updating the global state using mutations

Once we have our state and getters ready as explained in part II of this blog, we can start defining our mutations.

What is a mutation?

Mutations are functions that are used to change/ modify your Vuex store. You cannot directly call your mutations instead a mutation can be invoked using store.commit.

Defining our first mutation

In the previous part, we defined our state and the getters. Let's create a mutation handler called CLEAR_WALLET_BALANCE which sets the walletBalance in the state to 0.

// store/index.js
import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    userId: 101,
    deliveryPincode: "400068",
    walletBalance: 100.54,
    products: [
      { id: 1, title: "Samsung S21", price: 100 },
      { id: 2, title: "Macbook Pro", price: 200 },
      { id: 3, title: "Macbook Air", price: 300 },
      { id: 4, title: "Apple iPad", price: 400 },
      { id: 5, title: "Apple Watch", price: 500 },
    ],
    wishlist: [
      { id: 2, title: "Macbook Pro", isOutOfStock: false },
      { id: 4, title: "Apple iPad", isOutOfStock: true },
      { id: 5, title: "Apple Watch", isOutOfStock: true },
    ],
    cart: [
      { id: 4, title: "Apple iPad", price: 400, quantity: 1 },
      { id: 5, title: "Apple Watch", price: 500, quantity: 2 },
    ],
  },

  getters: {
    getCartTotalQuantity: (state) => {
      const quantities = state.cart.map((item) => item.quantity);
      return quantities.reduce((x, y) => x + y, 0);
    },
    getCartTotal: (state) => {
      const itemPrices
        = state.cart.map((item) => item.price * item.quantity);
      return itemPrices.reduce((x, y) => x + y, 0);
    },
    getWishlistOutOfStockItems: (state) =>
      state.wishlist.filter((item) => item.isOutOfStock).length,
  },

  mutations: {
    CLEAR_WALLET_BALANCE (state) {
      state.walletBalance = 0;
    }
  },

  actions: {},
});

We can call or commit this mutation inside our component using this.$store.commit. Now, whenever the button is clicked, the clearWalletBalance() method is called and this method will commit the CLEAR_WALLET_BALANCE mutation.

// ClearWalletBalance.vue
<template>
  <div>
    <button @click="clearWalletBalance">Clear Wallet Balance</button>
  </div>
</template>

<script>
export default {
  methods: {
    clearWalletBalance() {
      this.$store.commit('CLEAR_WALLET_BALANCE');
    }
  },
};
</script>

Committing using extra arguments

We can also pass a value to our mutation and this value can be set to any value in the state. For example, we can create a mutation to update the Pincode.

SET_DELIVERY_PINCODE (state, newPinCode) {
  state.deliveryPincode = newPinCode;
}

In the Component, the commit method will take the first parameter as the mutation name and the next parameter as the value.

// UpdatePinCode.vue
<template>
  <div>
    <input v-model="pinCode" type="text" />
    <button @click="updatePinCode">Update Pincode</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      pinCode: ""
    }
  },
  methods: {
    updatePinCode() {
      this.$store.commit('SET_DELIVERY_PINCODE', this.pinCode);
    }
  },
};
</script>

Similarly, we can also pass an Object to the mutation.

DECREMENT_WALLET_BALANCE (state, payload) {
  state.walletBalance = state.walletBalance - payload.amount;
}
this.$store.commit('DECREMENT_WALLET_BALANCE', {
  amount: this.amount
});

The mapMutations helper method

The mapMutations helper method can be used inside our component to list which mutations are required by the component.

<script>
import { mapMutations } from "vuex";

export default {
  methods: {
    ...mapMutations([
      'CLEAR_WALLET_BALANCE', 
      // maps `this.CLEAR_WALLET_BALANCE()` 
      // to `this.$store.commit('CLEAR_WALLET_BALANCE')`

      'SET_DELIVERY_PINCODE', 
      // maps `this.SET_DELIVERY_PINCODE(newPinCode)` 
      // to `this.$store.commit('SET_DELIVERY_PINCODE', newPinCode)`

      'DECREMENT_WALLET_BALANCE' 
      // maps `this.DECREMENT_WALLET_BALANCE(payload)` 
      // to `this.$store.commit('DECREMENT_WALLET_BALANCE', payload)`  
    ])
  },
};
</script>

Instead of a list as an argument to mapMutations, we can pass an Object with key as a method name and value as the mutation name. Now, the method name will be mapped to the mutation name.

<script>
import { mapMutations } from "vuex";

export default {
  methods: {
    ...mapMutations([
      clearWalletBalance: 'CLEAR_WALLET_BALANCE', 
      // maps `this.clearWalletBalance()` 
      // to `this.$store.commit('CLEAR_WALLET_BALANCE')`

      setDeliveryPincode: 'SET_DELIVERY_PINCODE', 
      // maps `this.setDeliveryPincode(newPinCode)` 
      // to `this.$store.commit('SET_DELIVERY_PINCODE', newPinCode)`

      decrementWalletBalance: 'DECREMENT_WALLET_BALANCE' 
      // maps `this.decrementWalletBalance(payload)` 
      // to `this.$store.commit('DECREMENT_WALLET_BALANCE', payload)`  
    ])
  },
};
</script>

Two-way computed property

We can also create two-way binding with our Vuex state using mutations. Here, we create a computed property where the getter will take the fetch the value from the state and the setter will call the mutation when any update occurs.

// UpdatePinCode.vue
<template>
  <div>
    <input v-model="pinCode" type="text" />
  </div>
</template>

<script>
export default {
  computed: {
    pinCode: {
      get () {
        return this.$store.state.deliveryPincode;
      },
      set (value) {
        this.$store.commit('SET_DELIVERY_PINCODE', value)
      }
    }
  }
};
</script>

This was all about mutations in Vuex. Next, we'll move to actions in Vuex!

Thanks for reading!