Telerik blogs
Vue

Learn how to build an eCommerce site that uses Vue for dynamically handling products and utilizes Vuex to correctly manage the state of your shopping cart.

Some people view the use of Vuex, a state management library, as quite a big step up from using Vue on its own. The concept of state management can sound a bit scary, and, to be fair, some state management libraries can be quite difficult to fully grasp (I’m looking at you, Flux and Redux!). 

Vuex, on the other hand, makes the process a whole lot easier to manage and should really be a tool that is utilized whenever required.

Some Assumptions

If you are reading this article, it is likely that you already know how to emit events from child components and know how to update state in a regular Vue app. So if you were tasked with building a shopping cart and wanted to be able to add items to it, you would know how to do so.

If not, it might be worth reading over this article that covers how to emit in Vue. Give that a read, then feel free to come back here once you feel comfortable with emitting events, as it is a super important concept to understand!

The Setup

Today we will be creating a mini eCommerce site/app with Vue and Vuex. We will be using Vue-cli to quickly scaffold our app. For those unaware of what Vue-cli is, check out the link to the official docs here. We’ve opted to use the manual set-up option within Vue-cli, which allows us to pick Vuex as an optional add-on. This means that Vuex will automatically be added to our app by default and it will also create a store.js file for us . This file will contain our app’s state data.

Note: Adding Vuex in this way is not a requirement, and you can otherwise choose to add Vuex via npm i vuex.

Let’s show you what our default store.js file looks like:

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

You’ll notice that just after the imports, we have Vue.use(Vuex).

This is super important, as it basically enables the ability to then give all of our child components access to our Vuex store through the use of this.$store. We complete this process by including our store inside of our Vue object, which we will see next.

So we also have a main.js file, which handles the rendering of Vue into our app. The file looks like this to begin with:

import Vue from 'vue'
import App from './App.vue'
import store from './store'
 
Vue.config.productionTip = false
 
new Vue({
  store,
  render: h => h(App)
}).$mount('#app')
 

As you can see, we import our Vuex store on line 3 and then add it inside of our new Vue object (see line 8) that gets rendered and mounted to the page. This completes the process of ‘injecting’ our store into every component.

We can go ahead and delete any bits of code that we don’t need, such as the HelloWorld.vue file, along with the Vue logo.

We then go about creating all of the components we are going to need. In essence, we will require an Item component, which will contain details of the item, along with a size-picker and an ‘add to cart’ button. These could have been made more modular by creating separate sub-components, but I have opted against this for brevity.

Once we’ve built all of our initial components, we have an app that looks like this:

Vue

All of our content is in place, and our items have their individual buttons - but nothing actually happens if any of the buttons are clicked. Let’s start building those parts with some super awesome Vuex state management!

Store

So our shopping cart is actually already returning information from our store which is great, as it means that the shopping cart is able to access data from our state. This isn’t something that is set up by default, though. So how is this working? Well let’s take a look at what we have set up so far.

App.vue

<template>
  <div id="app">
    <div class="header">
      <h1>The Boot Store</h1>
 
      <shopping-cart :cart="shoppingCart"></shopping-cart>
    </div>
 
    <section class="items-container">
    <item v-for="product in products"
      :key="product.key"
      :item="product"></item>
    </section>
  </div>
</template>

If we observe the bits of code above, it looks quite similar to how we would usually set this up using just plain old Vue.

On this assumption, it would be likely that the :cart=”shoppingCart” prop is holding data on the cart. And likewise, the v-for=”product in products” is looping through all of the products. This would be a correct assumption to make.

The only thing to remember here is that this data isn’t coming from inside of our root App.vue file. It’s coming from our store.js file. So how does it get there? Let’s take a look at our computed properties from App.vue below:

computed: {
 
    shoppingCart() {
        return this.$store.state.cart
    },
    products() {
        return this.$store.state.items
    }
}

Put simply, we create two functions that return data from this.$store. We then call these two computed functions inside of the template, which we saw previously. We could have skipped the process of creating these simple return functions by doing this instead:

:cart=”$store.state.cart”

and

v-for="product in $store.state.items"

And it would have still worked, but this can get unruly. It would also avoid the use case of computed properties in general - which is that you pass them data that gets cached, and if the data changes, the computed property will re-evaluate and return the new result. So we take advantage of this when writing our computed properties. It also has the added benefit of keeping our template view a bit cleaner.

Note: I should also mention that Vuex’s documentation talks about a mapState helper, which can be used in verbose apps that would otherwise need to lean on making lots and lots of computed property functions. Because our app is not going to be leaning on this too much, we will not be making use of mapState. If, however, you are reading this article with a view to building a huge application, I’d highly recommend reading up on mapState as it can be pretty useful! You can check out the link in the docs here. Ahead of time, I’ll also note that there are map helpers for all of the core concepts that we will be looking at in this article, but none will be used for the sake of brevity.

Okay, so computed properties inside of child components are being used here to simply return data from this.$store. That’s cool, but what about when we want to use computed properties like we normally do in Vue? Well, we could just write the same code that we normally do, but this wouldn’t be fully taking advantage of Vuex’s capabilities. We also want to be writing computed properties inside of our store.js that we can use throughout our application. So can we just write computed properties inside of store.js? Well, yes we can! But they look a little bit different. Enter getters!

Getters

Getters are essentially computed properties. Like computed properties, a getter’s result is cached based on its dependencies, and will only re-evaluate when some of its dependencies have changed. A slight difference with traditional computed properties is that the functions we create inside of getters will always need to be passed state as a parameter. We will take a look at an example that we be using inside of our eCommerce app after the next paragraph. 

So with our shopping cart, we want it to contain the contents of each product that gets added to it. But each item is likely to be an object (which contains the product’s ID, name, size and price). Our shopping cart is also going to display the total price. We can write a getter function that looks at the contents of the shopping cart, grabs the price of each item, adds them together and returns the sum. 

Let’s take a look:

getters: {
    total: state => {
        if(state.cart.length > 0) {
            return state.cart.map(item => item.price).reduce((total, amount) => total + amount);
        } else {
            return 0;
        }
}

Not sure how map and reduce work? I suggest you click here.

We’ve wrapped the return inside of an if statement, so that if the cart is empty, we show the total price as 0.

We then want to pass this.$store.getters.total down to the right place in our app. You’ll also notice that we’re referencing $store.getters this time instead of $store.state which makes sense since we just made a getter function.

Now we could pass this either straight into our ShoppingCart.vue, but let’s continue the initial design decision made earlier to create computed functions inside of App.vue that simply return the data held in the store. 

So let’s go ahead and add a function that does this:

totalAmount () {
    return this.$store.getters.total
}

This leaves our computed properties section inside of App.vue currently looking like this:

computed: {
    shoppingCart() {
        return this.$store.state.cart
    },
    products() {
        return this.$store.state.items
    },
    totalAmount () {
        return this.$store.getters.total
    }
}

Finally, we pass totalAmount down as a prop to ShoppingCart.vue by passing it to the <shopping-cart> tag inside of App.vue, like so:

<shopping-cart
    :cart="shoppingCart"
    :total="totalAmount">
</shopping-cart>

We can then reference the total in our ShoppingCart.vue component by simply writing this:

<p>Total:${{total}}</p>

And, just in case you were wondering, the dollar sign is here to simply put a literal dollar sign at the start of the price. It isn’t required for any sort of Vue syntax, such as this.$state - just thought I should clear that up!

So now our app is starting to come along quite nicely, and we’ve already utilized two of Vuex’s five core concepts!

Okay so we have our shopping cart displaying some data, but how about actually making the ‘Add to Cart’ buttons work so that we can go about adding things to our cart? Let’s take a look!

Mutations

The mutations property is kind of similar to the methods property that you would have in a standard Vue app. But when we use Vuex, we cannot modify anything inside of the store’s state directly. So in order to modify state, we must write a mutation that will handle this for us.

Similar to getter properties, we will be passing state as a parameter to any function that we create. In our case, we want to write a function that adds a product to our cart. The product in question will be added whenever a user clicks the ‘Add to Cart’ button that belongs to the particular product.

So far, our function looks like this:

mutations: {
  addToCart(state) {
  }
}

Now imagine that we were writing this app without Vuex. Our addToCart() function would likely emit some data along with it, in order for our state to know what product was being added to the cart. With Vuex, functions inside of our mutations can also accept an additional parameter which acts as a payload to carry some data with it.

So let’s add that in:

mutations: {
  addToCart(state, payload) {
  }
}

If “payload” sounds like a strange word, that’s because it is. In this context, it is basically the technical term for saying that we can send something into the function, like a string, an integer, an array, an object, etc.

We can then write a bit of code that simply pushes payload into our cart, like so:

mutations: {
  addToCart(state, payload) {
      return state.cart.push(payload);
  }
},

Okay, so we’ve written the mutation. 

But we can’t just go to our child components and write something like this.$store.mutations.addToCart, because that wouldn’t work. So how do we actually just call these mutation functions? Enter store.commit!

Committing mutations

So we are going to take a slightly different approach from some of the previous examples we have encountered with calling state and getters. We won’t be adding any sort of computed property that returns the function we just created. Instead, we are going to go straight into Item.vue and we’ll create a method. 

The method will have the same name of addToCart - though you should note that this wasn’t necessary. I simply felt it was appropriate to give the commit function the same name as the mutation function so that it was easier to remember. 

The function looks like this:

methods: {
    addToCart(item) {
        this.$store.commit('addToCart', item)
    },
}

What this is doing is simply calling the mutation that we made with the same name, and is passing it the item - which, if we remember from before, is basically the entire product object.

We then attach this onto the button inside of Item.vue as such:

<button @click="addToCart(item)">Add To Cart</button>

Now whenever we click on the ‘Add To Cart’ button, it adds the product object into the cart. The beauty here is that, whenever we add an item to the cart, the ‘No. of Items’ in the cart increases by 1 and the Total updates with the current total amount! How amazing is that?!

But we’re not finished yet. 

Improving our Payload

Although our item is being added to the cart, our function currently adds the entire contents of the product into the cart (so name, price, all available sizes, image, etc). It currently pays no attention to what size boot has been selected.

This obviously isn’t good. So let’s go fix that!

Now with the size picker, I have decided that this is something that would be better being handled inside of local state (i.e. inside of Item.vue). The reason being is that this is the only place where the selected size needs to reside, and we would be unnecessarily adding a lot of overhead here when it is not required. 

So with this in mind, we have added the following v-model to our size-picker part inside of Item.vue:

<select v-model="size">
    <option v-for="size in this.item.sizes" :key="size">{{size}}</option>
</select>

And then in the data part:

data() {
    return {
        size: ''
    }
},

This also has the added benefit of setting the default selected size to a blank string. So if we wanted, we could add some validation in to prevent a user from being able to add a pair of boots to the cart if a size has not been selected.

Now when a user picks a size, the size inside of data() will be updated. We are then going to pass this in to the payload we set up earlier.

As you may remember, the payload would automatically add the entire item object (including all of the sizes). We will edit this by manually passing in certain data, and, in doing so, will overwrite the part that takes in all of the sizes and will replace it with just the size that the user has selected. Let’s take a look:

methods: {
    addToCart(item) {
            this.$store.commit({
                type: 'addToCart',
                id: item.id,
                shoe: item.name,
                size: this.size,
                price: item.price
            })
    }
}

So this looks like quite a lot more code to set up a this.$store.commit, but essentially all we have done here is pass an object in to the commit instead. 

We set up a type, which is simply the name of the mutation. Then instead of passing the entire item, we pass in individual parts of the item. When we get to the size, we can then pass in this.size which will grab the selected size. In fact, we can add a little bit more to this to do the validation that we mentioned earlier:

methods: {
    addToCart(item) {
        if(this.size !== '') {
            this.$store.commit({
                type: 'addToCart',
                id: item.id,
                shoe: item.name,
                size: this.size,
                price: item.price
            })
        }
    }
}

So now, our code will only add an item to the cart if a size has been selected! How neat!

And What about Actions and Modules?

Actions and Modules are the two other core concepts in Vuex. Our shopping cart doesn’t really require these, so we won’t cover them in too much detail, but I’d still like to give you a brief overview of them.

Actions are similar to committing a mutation. The difference being that mutations are synchronous, so whenever we commit one, it will fire immediately. Actions are useful when we are dealing with asynchronous code. 

For example, if we needed to pull in data from an API before committing a mutation, we would look to utilize actions in conjunction with mutations. Our shopping cart application does not require this, but if yours does, I strongly recommend you read into the Vuex documentation on actions for a primer.

Modules are useful for those occasions when you are writing a complex application that has a lot of tentacles and has a ton of stuff going on. They allow you to break up your single Vuex store into smaller fragments in order to help it become more manageable and less unruly. Again, I recommend Vuex’s page on Modules for more information.

And There We Have It!

We have built an eCommerce application that use Vue for handling reactivity and, most importantly, utilizes Vuex to manage the state of the app!

If you would like to take a look at the code for this app, check out the Github repository here: https://github.com/sunil-sandhu/vuex-shopping-cart


For more info on Vue: Want to learn about creating great user interfaces with Vue? Check out Kendo UI for Vue our complete UI component library that allows you to quickly build high-quality, responsive apps. It includes all the components you’ll need, from grids and charts to schedulers and dials. 

Looking to use Vuex with Kendo UI for Vue? Check out this quick guide.


Sunil Sandhu
About the Author

Sunil Sandhu

Sunil Sandhu is a Full Stack Web Developer and Editor of Javascript In Plain English. When not building or writing about interactive experiences with the latest JS frameworks, Sunil acts as an advisor to start-ups and helps to train new developers.

Related Posts

Comments

Comments are disabled in preview mode.