Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

VUE watch triggered infinite loop

I'm new to using VUE.Js, and i created a very simple app to try out how it works.

The problem happens immediately where when i run the app, the watch for a variable is triggered in an infinite loop. I cannot figure out why. There is a v-for loop but that is on an array that only has two elements.

Initially the SubTotal should be 0. But as soon as the app is run, it triggers the Buy method, even though i haven't clicked the buy button and the sub total ends up being 442.37999999999965.

Thanks for any help.

Here is the jsfiddle Beer shopping cart

HTML :

<div id = "growler">      
  <table>
    <tr>
      <th style='width:150px'>Beer</th>
      <th style='width:50px'>Price</th>
      <th style='width:30px'></th>
    </tr>

    <tr v-for = "beer in beers">
      <td>{{ beer.name }}</td>
      <td>{{ beer.price }}</td>
      <td>        
        <button :click="buy(beer)">buy</button>        
      </td>
    </tr>

    <tr>
      <td>SubTotal</td>
      <td>{{subTotal}}</td>
      <td></td>
    </tr>
  </table>
</div>

JS:

  new Vue({
  el: "#growler",
  data: {
      beers: [
        {name: 'Ahool Ale', price: 2.00}, 
        {name: 'Agogwe Ale', price: 2.38}        
      ],
      shoppingCart: [], 
      subTotal: 0.00        
  }, 
  watch: {
    shoppingCart: function() {
        console.log('shopping cart watch triggered');
      this.updateSubTotal();
    }
  }, 
  methods: {
    updateSubTotal: function () {
      var s=this.shoppingCart.length;
      var t=0;
      for (var i=0;i<s; i++){
        t += this.shoppingCart[i].price;
      }
      this.subTotal = t;
    }, 
    buy: function (beer) {
        console.log('beer pushed on array');
      this.shoppingCart.push(beer);
    }
  },   
  beforeCreate: function() {
    console.log('beforeCreate');
  },
  created: function() {
    console.log('created');                    
  },
  beforeMount: function() {
    console.log('beforeMount');                    
  },
  mounted: function() {
    console.log('mounted');                    
  },
  beforeUpdate: function() {
    console.log('beforeUpdate');
  },
  updated: function() {
    console.log('updated');
  },
  beforeDestroy: function() {
    console.log('beforeDestroy');
  },
  destroyed: function() {
    console.log('afterDestroy');
  }

});
like image 623
user1161137 Avatar asked Mar 06 '23 19:03

user1161137


1 Answers

I found your mistake:

<button :click="buy(beer)">buy</button>  

You used :(v-bind) instead of @(v-on:) on the click handler.

When you first bind it, the function is called once and updates the shoppingCart. This will update the subTotal data, which will force a re-render of the DOM, which will trigger the buy function again because of the :bind.

Fix:

<button @click="buy(beer)">buy</button>  
<!-- or -->
<button v-on:click="buy(beer)">buy</button>  

Suggested changes for your code:

Use computed properties instead of a method to update a property that represents a sum of other values:

new Vue({
  el: "#growler",
  data: {
    beers: [{
        name: 'Ahool Ale',
        price: 2.00
      },
      {
        name: 'Agogwe Ale',
        price: 2.38
      }
    ],
    shoppingCart: []
  },
  watch: {
    shoppingCart: function() {
      console.log('shopping cart watch triggered');
    }
  },
  computed: {
    subTotal: function() {
      return this.shoppingCart.reduce(function(total, beer) {
        return total + beer.price;
      }, 0);
    }
    }
  },
  methods: {
    buy: function(beer) {
      this.shoppingCart.push(beer);
    }
  },

});
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
<div id="growler">
  <button>buy</button>
  <table>
    <tr>
      <th style='width:150px'>Beer</th>
      <th style='width:50px'>Price</th>
      <th style='width:30px'></th>
    </tr>

    <tr v-for="beer in beers">
      <td>{{ beer.name }}</td>
      <td>{{ beer.price }}</td>
      <td>
        <button @click="buy(beer)">buy</button>
      </td>
    </tr>

    <tr>
      <td>SubTotal</td>
      <td>{{subTotal}}</td>
      <td></td>
    </tr>
  </table>
</div>
like image 189
Phiter Avatar answered Mar 27 '23 13:03

Phiter