Dan
Dan

Reputation: 5986

Computed function being called twice in vuejs app

I'm learning some VueJS by implementing a simple shopping cart.

Here's my HTML:

<div id="app">
  <table>
    <thead>
      <tr>
        <th>Product</th>
        <th>Unit price</th>
        <th>Quantity</th>
        <th>Total</th>
      </tr>
    </thead>
    <tbody>
      <tr v-for="(cartItem, index) in order.cartItems">
        <td>{{ cartItem.productName }}</td>
        <td>{{ cartItem.unitPrice }}</td>
        <td>{{ cartItem.quantity }}</td>
        <td>{{ lineTotal[index] }}</td>
      </tr>
      <tr class="shipping">
        <td>Shipping</td>
        <td>{{ shipping }}</td>
        <td>{{ totalItems }}</td>
        <td></td>
      </tr>
    </tbody>
    <tfoot>
      <tr>
        <td colspan="3">TOTAL</td>
        <td></td>
      </tr>
    </tfoot>
  </table>
</div>

Here's the JS:

const shipping = 1.5;

const app = new Vue({
  el: '#app',
  data: {
    shipping: shipping.toFixed(2),
    order: []
  },
  created() {
    fetch('https://api.myjson.com/bins/1fk6ua')
      .then(response => response.json())
      .then(json => {
      this.order = json.order
    })
  },
  computed: {
    lineTotal: function () {
      return this.order.cartItems.map(function (cartItem) {
        return (cartItem.unitPrice * cartItem.quantity).toFixed(2);
      });
    },
    totalItems: function(){
      return this.order.cartItems.reduce(function (totalItems, cartItem) {
        return totalItems + parseInt(cartItem.quantity);
      }, 0);
    }
  }
});

Here's a Fiddle: https://jsfiddle.net/8fg70ud2/.

As you can see I'm only part way through the implementation. I've got the product line totals working by implementing a computed function called lineTotal. Now I'm trying to get the shipping line working by first getting the number of products in the cart (which will ultimately be multiplied by the shipping constant). I've got as far as implementing a totalItems function which seems to do the job, but I notice there's now a console error:

TypeError: Cannot read property 'reduce' of undefined

Digging a bit deeper, it seems that the totalItems function is being called twice; the first time this.order.cartItems is undefined hence the reduce call is erroring.

Why is this happening? I may well be going about all of this in the wrong way so am open to suggestions as to how to move forward; I'm at the very start of the learning curve :)

Thanks.

Upvotes: 6

Views: 6016

Answers (1)

Prashant
Prashant

Reputation: 8040

When the Vue instance is created, it has a property order which you initialized to []. In your v-for you try to get order.cartItems which also results in undefined but you don't notice any problem there, because Vue bails out from creating the structure inside v-if never hitting your another computed property lineTotal.

But, totalItems is computed right away. With order still an []. order.cartItems is undefined. Which obviously does not have reduce method. Hence, the error.

Then, in the created callback, you actually fetch the data and populate the order property with the actual object. Since order property is the dependency of the computed property totalItems and it has updated, Vue will compute the property again and this time order property has the correct data structure. And, things will go fine in the second run.

The solution is quite simple. Wait until your order property is populated by using v-if="order.cartItems" in the containing tr element with the class .shipping.

Upvotes: 7

Related Questions