3

In the snippet below I have two Vue components, each with an items array containing two objects. Each component's template is set up to show an object of the items array if that object's show property is truthy. The first object in the array initially has a show value of true. The second object does not have a show value set.

new Vue({
  el: '#ex1',
  data: () => ({ items: [{name: 'apple', show: true}, {name: 'banana'}] }),
  mounted() {
    // 2nd item's show property setting is not detected here
    this.items[1].show = true; 
  }
})

new Vue({
  el: '#ex2',
  data: () => ({ items: [{name: 'apple', show: true}, {name: 'banana'}] }),
  mounted() {
    // 2nd item's show property setting IS detected...
    this.items[1].show = true; 
    
    // ...when the 1st item's show property is set to false
    this.items[0].show = false; 
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.min.js"></script>
<div id="ex1">
  Example 1:
  <div v-if="items[0].show">{{ items[0] }}</div>
  <div v-if="items[1].show">{{ items[1] }}</div>
</div>

<div id="ex2">
  Example 2:
  <div v-if="items[0].show">{{ items[0] }}</div>
  <div v-if="items[1].show">{{ items[1] }}</div>
</div>

In the first example, I set the show property of the second object in the items array to true. Vue does not detect the change in the template, which is expected.

In the second example, I set the show property of the second object to true, and then set the show property of the first object to be false. In this case, Vue does detect the change in both elements of the items array and updates the template accordingly. I would not expect this to be the case, given Vue's Change Detection Caveats.

This seems like a pretty innocuous bug (if it even qualifies as a bug), so I'm not sure if it's worth reporting. But, I was wondering if anyone would be able to explain this behavior. Why does Vue detect the addition of the show property to the object in the second example?

0

2 Answers 2

5

Changing the reactive property (show of the first object) triggers a re-render. Vue faithfully renders the current state of the objects referenced by the template, which includes the second object.

The second object's show property is still not reactive, and changes to it will not update the Vue. It's simply the change to the state of the reactive property on the first object that triggers the update.

In other words, changing the show property of the second object, because it was added after the fact, will never update the Vue, but changing the show property on the first object will always update, and the update will just render the current state of the array no matter how it was added.

I wouldn't consider this a bug.

One easy way to kind of see the difference is to log the array to the console after the update. You will see the show property of the second object is not observed, but is nevertheless there.

Edit I wanted to clarify slightly based on a comment. The reason the changes to the second object appear in the Vue is because the entire Vue is re-rendered and the second object is referenced directly in the Vue. If you, for example, passed the value down to a component, the second object would not be re-rendered.

Here is an example showing that the second object is never updated when the value is passed to a component (because the component manages the rendering).

console.clear()
 
 Vue.component("item", {
  props: ["item"],
  template: `
    <div>Item Show: {{item.show}}</div>
  `
 })
 
 new Vue({
      el: '#ex2',
      data: () => ({ items: [{name: 'apple', show: true}, {name: 'banana'}] }),
      mounted() {
        // 2nd item's show property setting IS detected...
        this.items[1].show = true; 
        
        // ...when the 1st item's show property is set to false
        this.items[0].show = false; 
      }
    })
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>

<div id="ex2">
  This will show false, because that is the current state and this object's show
  property is reactive
  <item :item="items[0]"></item>
  <hr>
  This will not show *anything*, despite the current state of show=true
  because the object's show property is not reactive
  <item :item="items[1]"></item>
</div>

Sign up to request clarification or add additional context in comments.

3 Comments

I would not have assumed that the whole array would be re-rendered from an update to just one of its objects. But that explanation makes sense!
@thanksd really its the fact that the template is triggered to re-render; so everything referenced by the template will be re-rendered. I think if each object were passed down to a component, then only the one with the reactive property would be re-rendered, but I would have to test it.
@thanksd Added an example that shows what I mean in the above comment.
-1

as described in the link you post:

Vue does not allow dynamically adding new root-level reactive properties to an already created instance. However, it’s possible to add reactive properties to a nested object using the Vue.set(object, key, value)

to solve instead of this.items[1].show = true you can write:

Vue.set(this.items[1], 'show', true)

hope it's clear

1 Comment

That's not the issue here. I understand why my first example isn't reacting to the property addition and how to use Vue.set to fix it. My question is why my second example is reacting to the property addition even though I haven't used Vue.set.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.