pzjzeason
pzjzeason

Reputation: 139

v-show doesn't work as expected

When the array shows[] changes after I click the button(I can see the shows[] changes in the Vue's chrome plugin), character 'a' is still on the page.'b' and 'c' never show up.

<script type="text/javascript" src="https://unpkg.com/[email protected]/dist/vue.js"></script>
<script>
    let vm = new Vue({
        el: '#app',
        data: {
        currentShow:0,
        shows:[true, false, false],
        },
        watch:{
            currentShow: function (val, old) {
                this.shows[old] = false;
                this.shows[val] = true
            }
        }
    });
</script>
<!-- Letter 'a', 'b', 'c' shows only when its corresponding shows[] is true-->
<div id="app">
    <p v-show="shows[0]">a</p>
    <p v-show="shows[1]">b</p>
    <p v-show="shows[2]">c</p>
    <button @click="currentShow=(currentShow+1)%3">next</button>
</div>

Upvotes: 2

Views: 7312

Answers (2)

xdrm
xdrm

Reputation: 399

Vue cannot watch on array elements if they are primitives (not objects typically). Vue updated is triggered by push(), pop() and other methods but it cannot know when you update a value inside. See the docs.

I found a specific solution for your example, where it can only be one selected item, feel free to adapt it.

let vm = new Vue({
    el: '#app',
    data: {
      currentShow: 0
    },
    methods: {
    
      showNext(){
        this.currentShow = (this.currentShow+1) % 3
      }
      
    }
});
<script type="text/javascript" src="https://unpkg.com/[email protected]/dist/vue.js"></script>

<!-- Letter 'a', 'b', 'c' shows only when its corresponding shows[] is true-->
<div id="app">
    <p v-show="currentShow==0">a</p>
    <p v-show="currentShow==1">b</p>
    <p v-show="currentShow==2">c</p>
    <button @click="showNext()">next</button>
</div>

Upvotes: 0

acdcjunior
acdcjunior

Reputation: 135742

That is a reactivity caveat. You could use Vue.set():

<script type="text/javascript" src="https://unpkg.com/[email protected]/dist/vue.js"></script>

<!-- Letter 'a', 'b', 'c' shows only when its corresponding shows[] is true-->
<div id="app">
    <p v-show="shows[0]">a</p>
    <p v-show="shows[1]">b</p>
    <p v-show="shows[2]">c</p>
    <button @click="currentShow=(currentShow+1)%3">next</button>
</div>

<script>
    let vm = new Vue({
        el: '#app',
        data: {
            currentShow: 0,
            shows:[true, false, false],
        },
        watch:{
            currentShow: function (val, old) {
                Vue.set(this.shows, old, false);
                Vue.set(this.shows, val, true);
            }
        }
    });
</script>

Docs:

If you add or edit elements by index, you have to call Vue.set():

Vue.set(this.shows, old, false);
Vue.set(this.shows, val, true);

Or:

this.shows.splice(old, 1, false);
this.shows.splice(val, 1, true);

This enables Vue to adjust the reactivity to that element.

Why?

Besides regular caveat problems, the docs have a specific guidance on arrays:

Caveats

Due to limitations in JavaScript, Vue cannot detect the following changes to an array:

  1. When you directly set an item with the index, e.g. vm.items[indexOfItem] = newValue
  2. When you modify the length of the array, e.g. vm.items.length = newLength

For example:

var vm = new Vue({
  data: {
    items: ['a', 'b', 'c']
  }
})
vm.items[1] = 'x' // is NOT reactive
vm.items.length = 2 // is NOT reactive

To overcome caveat 1, both of the following will accomplish the same as vm.items[indexOfItem] = newValue, but will also trigger state updates in the reactivity system:

// Vue.set
Vue.set(vm.items, indexOfItem, newValue)

// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)

You can also use the vm.$set instance method, which is an alias for the global Vue.set:

vm.$set(vm.items, indexOfItem, newValue)

To deal with caveat 2, you can use splice:

vm.items.splice(newLength)

Upvotes: 2

Related Questions