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!