Search code examples
vue.jsvuexback-buttonproduct-variations

Vue shopping cart product variations adding to cart but item in cart changes instead of adding new item


I have a vue version "^2.6.11" app with vuex where I am adding items to the cart. My items have variations like colour so I have two conditions before adding to cart comparing the colour and the product ID.

product.productId == item.productId && product.colour == item.colour

The colour is chosen from a select box, a property on the product is set with the selected colour on change event from the select box.

this.item.colour = event.target.value

The problem is that it updates the current item in the cart even if the colour is changed for example:

  • Select box displays gold, "add to cart" is clicked and a gold item is added to cart.

  • Select box is changed to silver, the product component changes its colour property to silver and "add to cart" is clicked.

  • The current item in the cart quantity changes to 2 and its colour changes to silver.

The odd thing is when I navigate to the checkout then click the back button, the add to cart works the way I want it to by adding the new variation of the item to the cart, however, when I add another variation after that it works as it did the first time.

I hope this makes sense. I don't understand why it doesn't work prior to click the back button.

I can't set up a Jsfiddle or code snippit because there are so many components working together here so I will add some of my code below.

// Product.vue

<template>
  <div>
    <b-col>
      <b-card
        v-bind:title="product_type"
        v-bind:img-src="imagePath"
        img-alt="Image"
        img-top
        tag="article"
        style="max-width: 15rem;"
        class="mb-2">
        <b-card-text>{{ product_type }}</b-card-text>
        <div class="form-group">
          <label for="colourSelect">Please select a colour</label>
          <select @change="onChange($event)">
            <option v-for="item in metal_colour" v-bind:key="item">
              {{ item }}
            </option>
          </select>
        </div>
        <div class="clearfix">
          <div class="price pull-left" v-bind:key="price">€{{ price }}</div>
          <div class="text-right">
            <b-button @click="addToCart" variant="primary">Add to cart</b-button>
...
</template>
<script>
export default {
  name: 'Product',
  props: {
    productId: Number,
    imagePath: String,
    product_type: String,
    metal_colour: Array,
    price: Number,
    qty: Number
  },
  data() {
    return {
      item: {
        productId: this.productId,
        imagePath: this.imagePath,
        product_type: this.product_type,
        price: this.price,
        colour: 'Gold',
        qty: 1
      }
    }
  },
  methods: {
    addToCart() {
      this.$store.commit('addToCart', this.item)
      this.$bvModal.show('modal-1')
    },
    onChange(event) {
      this.item.colour = event.target.value
    }
  }
}
</script>
// store/index.js Vuex

...
 mutations: {
    addToCart(state, item) {
      let found = state.inCart.find(
        product =>
          product.productId == item.productId && product.colour == item.colour
      )
      if (found) {
        found.qty++
      } else {
        state.inCart.push(item)
      }
    }
  },
...

For my shopping cart I am using a modal and the items are listed as follows:

// Cart.vue
<div>
  <b-table striped :items="this.$store.state.inCart">

    <template v-slot:cell(imagePath)="data">
      <img :src="data.value" class="thumb" alt="product" />
      {{ data.imagePath }}
     </template>

  </b-table>
</div>

<div slot="modal-footer">

  <router-link to="/checkout">
    <b-button variant="primary" @click="$bvModal.hide('modal-1')">
      Checkout
    </b-button>
  </router-link>

  <b-button class="modalBtn" variant="primary" @click="$bvModal.hide('modal-1')">
    Continue Shopping
  </b-button>

</div>


Solution

  • You used this > this.$store.state.inCart in the <template> markup, should be just $store.state.inCart but that should have flagged an error anyway and never actually work.

    <b-table striped :items="$store.state.inCart">
    

    You might be having reactivity issues, try this instead:

    import {mapState} from 'vuex'

    computed: {
    
      ...mapState(['inCart'])
    
    }
    

    <b-table striped :items="inCart">


    Finally, When you add an item to your cart, you need to add a COPY of the item object because you are passing the object by reference and getting unexpected behavior as a result of doing state.inCart.push(item).

    There are two options:

    1) import lodash > import { copy } from 'lodash' and use copy(item)

    2) JSON.parse(JSON.stringify(item)) looks dirty but is fast.

    These two approaches will de-reference the object.

    To clarify, you can do this copy process inside your vuex addToCart method:

    addToCart(state, item) {
    
      let found = state.inCart.find(
        product => product.productId === item.productId && product.colour === item.colour
      )
    
      if (found) {
    
        found.qty++
    
      } else {
    
        // Clone the object.
        const insert = JSON.parse(JSON.stringify(item))
        state.inCart.push(insert)
    
      }
    }