3

I am getting the following infinite loop warning. I understand that it is probably because I am changing a variable in my template's for loop due to a method call. Any idea how to fix it? The loop does complete, so it's not actually an infinite loop, but I'd like to fix the warning.

[Vue warn]: You may have an infinite update loop in a component render function.

Code Snippet:

new Vue({
  el: '#app',
  data: {
    contents: {"34": {"id": 34, build_name: "email_simple", build_readable: "Email"},"35": {"id": 35, build_name: "email_complex", build_readable: "Email"},"36": {"id": 36, build_name: "email_half", build_readable: "Email"}},
    last_build_type: '',
    contents_tree: [34,35,36]
  }, 
  methods: {
      checkBuildType(id){
        let check = false;
        if(this.last_build_type !== this.contents[id].build_name){
            check = true
        }
        this.last_build_type = this.contents[id].build_name;
        return check
      }

  }
  
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
  <template v-for="(id, i) in contents_tree">
      <div v-bind:key="i + '_' + id" class="inline">
          <template v-if="checkBuildType(id)">
              {{i}} - {{id}} -
              {{contents[id].build_readable}}
              <br>
            
          </template>

    </div>
  </template>
</div>
AlexMA
  • 9,842
  • 7
  • 42
  • 64
Jordan
  • 1,422
  • 1
  • 11
  • 21
  • what is `contents_tree`? I believe we need more code to see what is actually causing the error – depperm Mar 25 '20 at 12:35
  • i think checkBuildType will alwayse returns true because new build would be different from current if i'm understanding your code correctly. – Harsh Srivastava Mar 25 '20 at 12:39
  • @depperm I have added an example ```contents_tree``` and an example ```contents```. – Jordan Mar 25 '20 at 12:41
  • It is possible for two types to repeat back-to-back. – Jordan Mar 25 '20 at 12:42
  • Provide all the component, probably this loop is caused by computedA depends on computedB which depends on computedC. And you are not supposed to change stuff in computed. – Alex Brohshtut Mar 25 '20 at 12:43
  • Please post the exact error message – adiga Mar 25 '20 at 12:48
  • I have added the rest of the code. There is more code on the page, but I have commented it all out and the error is coming from here. I added the exact error message. – Jordan Mar 25 '20 at 12:50
  • 1
    Happy to help but please provide a runnable codepen or other complete snippet that reproduces the bug.https://stackoverflow.com/help/minimal-reproducible-example – AlexMA Mar 25 '20 at 12:53
  • for `contents` the keys should be strings [see](https://stackoverflow.com/a/3633390/3462319) – depperm Mar 25 '20 at 13:02
  • 1
    @AlexMA I have added the code snippet which is generating the error. – Jordan Mar 25 '20 at 13:16

1 Answers1

4

You are getting that warning because Vue has to re-render for each item in the v-for loop, due to the fact that the for loop updates the component's state. My solution is to compute the result for each array item in one pass, in a computed property (basically an index object), and access that computed property in the v-for rather than using the checkBuildType method.

new Vue({
  el: '#app',
  data: {
    contents: {
      "33": {
        "id": 33,
        build_name: "email_half",
        build_readable: "Email"
      },
      "34": {
        "id": 34,
        build_name: "email_simple",
        build_readable: "Email"
      },
      "35": {
        "id": 35,
        build_name: "email_complex",
        build_readable: "Email"
      },
      "36": {
        "id": 36,
        build_name: "email_half",
        build_readable: "Email"
      },
      "37": {
        "id": 37,
        build_name: "email_half",
        build_readable: "Email"
      }
    },
    last_build_type: '',
    contents_tree: [34, 35, 36, 37, 33]
  },
  computed: {
    buildTypes() {
      const buildTypesMap = {};
      for (id of this.contents_tree) {
        buildTypesMap[id] = this.checkBuildType(id);
      }
      return buildTypesMap
    }
  },
  methods: {
    checkBuildType(id) {
      let check = false;
      if (this.last_build_type !== this.contents[id].build_name) {
        check = true
      }
      this.last_build_type = this.contents[id].build_name;
      return check
    }

  }

})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
  <template v-for="(id, i) in contents_tree">
      <div v-bind:key="i + '_' + id" class="inline">
          <template v-if="buildTypes[id]">
              {{i}} - {{id}} -
              {{contents[id].build_readable}}
              <br>
            
          </template>

</div>
</template>
</div>
AlexMA
  • 9,842
  • 7
  • 42
  • 64
  • The ```contents_tree``` determines the order. I tested the code you wrote and it checks the ```last_build_type``` for the order of the content object, but not the order of the ```contents_tree``` array. For example, if you change ```contents_tree``` to ```contents_tree=[34, 35, 36, 36] ``` it won't be able to tell what the last item in the order was. Any ideas on that? – Jordan Mar 25 '20 at 14:22
  • @Jordan I don't seem to follow. As long as the ids in contents_tree match the ids in contents, it will work. the computed prop builds up a map like `{ 34: true, 35: false ... }`. In the template it is using brackets for object access. So `buildTypes[34] => true`. It should work even if there are many duplicates. This computed property doesn't do anything to the order. – AlexMA Mar 25 '20 at 14:26
  • The ```contents_tree``` order will not necessarily follow the order of the ```contents``` object. That is why ```contents_tree``` exists. Otherwise I would just use the object's order. For example, if you add in a new first entry to ```contents```: ```"33": "id": 33, build_name: "email_half", build_readable: "Email"```. Then have ```contents_tree=[34,35,36,33]```. In this case, the ``` – Jordan Mar 25 '20 at 14:38
  • The new computed property is not an array, so order doesn't matter. And it will be recomputed every time as long as your reactivity is properly wired up (which can be tricky in vue - make sure you're using Vue.set if you're adding new items to the array after mount). I understand what you are saying, but I don't think it correctly applies to my solution. If you're still convinced your app has correct reactivity and my answer is not correct, please provide a reproducible codepen or edit your question and I will fix my answer. – AlexMA Mar 25 '20 at 14:43
  • Here is a codepen: https://codepen.io/codingcanada/pen/JjdePZj You can see I added 33 and 37. 37 doesn't show up, which is correct. But 33 does, because the ```contents_tree``` order doesn't follow the order of the original ```contents``` object. – Jordan Mar 25 '20 at 14:50
  • Thanks for codepen and for the good communication. I think I finally understand - the checkBuildType function is not idempotent so order of execution matters, and I executed it in the wrong order. I believe the new CP does this correctly. – AlexMA Mar 25 '20 at 15:02