Vuex - Vue State Management - Part II - State & Getters

Vuex - Vue State Management - Part II - State & Getters

Fetching & binding the global state into Vue components

Setting up the state and the getters are the next thing on our list after we have set up the store for our project as explained in part I of this blog.

What is a state?

The state in our store is an object. It is like the data we have in an individual component but a state can be used application-wide. Having an application-level state serves as the single source of truth which makes it easier to get the current application state at any point in time.

Let's take an example of an E-commerce platform and initialize the state with the data we need.

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: {},

  mutations: {},

  actions: {},
});

Once we have defined the initial state of our application in the store, we need to fetch this data in our Vue components.

In the Products.vue component, we want to access the products listing and the wishlist.

The way to achieve this is by creating computed properties.

A Vue component can directly access the store using this.$store.

Using this information, we create a computed property called products which return this.$store.state.products and another computed property called wishlist which return this.$store.state.wishlist.

The Vue component is now directly listening to the state in our store.

// Products.vue

<template>
  <div>
    <h1>Products:</h1>
    <div v-for="product in products" :key="product.id">
      {{ product.id }} - {{ product.title }} - {{ product.price }}
    </div>

    <p>{{ wishlist.length }} items wishlisted.</p>
  </div>
</template>

<script>
export default {
  computed: {
    products() {
      return this.$store.state.products;
    },
    wishlist() {
      return this.$store.state.wishlist;
    },
  },
};
</script>

A component may want to fetch a lot of different values from the store. According to the above method, if there are 10 values to be fetched from the store, you will have to create 10 computed properties.

To make things less repetitive, Vuex has a mapState helper function.

The mapState function takes an array of strings which are the keys that you want to access from the store. Since the mapState function returns an object, you can use the spread operator to use it in combination with other local computed properties that the component may have.

// Products.vue

<template>
  <div>
    <h1>Products:</h1>
    <div v-for="product in products" :key="product.id">
      {{ product.id }} - {{ product.title }} - {{ product.price }}
    </div>

    <p>{{ wishlist.length }} items wishlisted.</p>
  </div>
</template>

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

export default {
  computed: {
    ...mapState(["products", "wishlist"]),
  },
};
</script>

Looks better! Any change in the state will now reflect in our component. That's how you create a state and get the values inside a component.

What are getters?

Let's say we want to display the cart's total quantity in our component.

We create a computed property in our component, get the cart array using this.$store.state.cart in this computed property, calculate the total quantity from the cart, and return this value.

Works! Now we want this value in 3 other components. The other components will also have to perform the same steps as above. Duplicate code!

Getters to the rescue! The getters are like the computed properties for the stores. The getter's result will only re-evaluate if some of its dependencies (the state values used by the getter) have changed, similar to how computed properties work.

We define 3 getters in our store:

  • getCartTotalQuantity: returns the total quantity of the cart.
  • getCartTotal: returns the total amount of the cart.
  • getWishlistOutOfStockItems: returns the count of the out of stock items in the wishlist
// 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: {},

  actions: {},
});

In the Cart.vue component, we fetch the cart, deliveryPincode and the walletBalance using the mapState helper.

Similar to fetching the state, this.$store.getters can be used to map the getters.

We create a computed property called cartTotalQuantity which return this.$store.getters.getCartTotalQuantity and another computed property called cartTotal which return this.$store.getters.getCartTotal.

// Cart.vue

<template>
  <div>
    <p>Delivery Pincode: {{ deliveryPincode }}</p>
    <p>Wallet Balance: INR {{ walletBalance }}</p>

    <h1>Cart:</h1>
    <div v-for="cartItem in cart" :key="cartItem.id">
      {{ cartItem.id }} - {{ cartItem.title }} - {{ cartItem.price }} -
      {{ cartItem.quantity }}
    </div>

    <p>Total Quantity: {{ cartTotalQuantity }}</p>
    <p>Total Amount: {{ cartTotal }}</p>
  </div>
</template>

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

export default {
  computed: {
    ...mapState(["cart", "deliveryPincode", "walletBalance"]),
    cartTotalQuantity() {
      return this.$store.getters.getCartTotalQuantity;
    },
    cartTotal() {
      return this.$store.getters.getCartTotal;
    },
  },
};
</script>

Vuex also provides a helper function called mapGetters to easily store the getters to local computed properties.

The mapGetters function takes an object as an argument, and each key in the object is the local computed property name and the value is the corresponding getter function from the store as a string.

Let's update the above code using the mapGetters function.

// Cart.vue

<template>
  <div>
    <p>Delivery Pincode: {{ deliveryPincode }}</p>
    <p>Wallet Balance: INR {{ walletBalance }}</p>

    <h1>Cart:</h1>
    <div v-for="cartItem in cart" :key="cartItem.id">
      {{ cartItem.id }} - {{ cartItem.title }} - {{ cartItem.price }} -
      {{ cartItem.quantity }}
    </div>

    <p>Total Quantity: {{ cartTotalQuantity }}</p>
    <p>Total Amount: {{ cartTotal }}</p>
  </div>
</template>

<script>
import { mapState, mapGetters } from "vuex";

export default {
  computed: {
    ...mapState(["cart", "deliveryPincode", "walletBalance"]),
    ...mapGetters({
      cartTotalQuantity: "getCartTotalQuantity",
      cartTotal: "getCartTotal",
    }),
  },
};
</script>

It's that easy to map the store to your components! In the upcoming blogs, we'll see how to update the store from the components.

Thanks for reading!