[Vuejs]-You may have an infinite update loop in watcher with expression "tabs" error in vue

0👍

For example, assuming the initial value for this.tabs='1', if this.edit is true, and let this.tabs='11', so the newVal of the watch will be '11', and oldVal of the watch will be '1'; then this.tabs will be changed to this.tabs='1' because of this.edit is true. now newVal='1', oldVal='11', finally it will fall into one infinite loop.

Vue.config.productionTip = false
new Vue ({
  el:'#app',
  data () {
      return {
        tabs: '1',
        edit: true
      }
  },
  watch: {
    tabs: function (newVal, oldVal) {
      console.log(newVal, oldVal)
      if (this.edit) this.tabs = oldVal
    }
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
    <div>
        <div>
            <input type="text" value="A" v-model="tabs">
        </div>
        <pre>{{tabs}}</pre>
    </div>
</div>

One workaround is uses the setter of one computed property.

Vue.config.productionTip = false
new Vue ({
  el:'#app',
  data () {
      return {
        tabs: '1',
        edit: true,
        tabsCopy: this.tabs
      }
  },
  computed: {
    myTabs: {
      get: function () {
        return this.tabs
      },
      set: function (newVal) {
        if (this.edit) {
          this.$forceUpdate()
          return
        }
        this.tabs= newVal
      }
    }
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
    <div>
        <div>
            <input type="checkbox" v-model="edit">
            <input type="text" v-model="myTabs">
        </div>
    </div>
</div>

Another solution is perform the validation in @input.

Vue.config.productionTip = false
new Vue ({
  el:'#app',
  data () {
      return {
        tabs: '1',
        edit: true,
        tabsCopy: this.tabs
      }
  },
  methods: {
    changeValue: function (newVal) {
      if(this.edit) { this.$forceUpdate(); return}
      this.tabs = newVal.target.value
    }
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
    <div>
        <div>
            <input type="checkbox" v-model="edit">
            <input type="text" :value="tabs" @input="changeValue($event)">
        </div>
    </div>
</div>

The worse workaround is uses one data property to check if newVal matches it, if yes, return immediately.

Anyway, it is not a good idea to use one watch to determinate its watch object if need to be changed or not.

So uses the solution setter of computed property will be better.

Vue.config.productionTip = false
new Vue ({
  el:'#app',
  data () {
      return {
        tabs: '1',
        edit: true,
        tabsCopy: this.tabs
      }
  },
  watch: {
    tabs: function (newVal, oldVal) {
      if (newVal == this.tabsCopy) return
      if (this.edit) {
        this.tabs = oldVal
        this.tabsCopy = this.tabs
      }
    }
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
    <div>
        <div>
            <input type="checkbox" v-model="edit">
            <input type="text" v-model="tabs">
        </div>
    </div>
</div>

Leave a comment