Home > Software engineering >  React to object property change in an Array in Vue 3
React to object property change in an Array in Vue 3

Time:11-11

I have a Vue 3 app. In this app, I need to show a list of items and allow a user to choose items in the array. Currently, my component looks like this:

MyComponent.vue

<template>
  <div>
    <div v-for="(item, index) in getItems()" :key="`item-${itemIndex}`">
      <div class="form-check">
        <input class="form-check-input" :id="`checkbox-${itemIndex}`" v-model="item.selected" />
        <label class="form-check-label" :for="`checkbox-${itemIndex}`">{{ item.name }} (selected: {{ item.selected }})</label>
      </div>
    </div>

    <button class="btn" @click="generateItems">Generate Items</button>
  </div>
</template>

<script>
  import { reactive } from 'vue';

  export default {
    data() {
      return itemCount: 0
    },

    methods: {
      generateItems() {
        this.itemCount = Math.floor(Math.random() * 25)   1;
      },

      getItems() {
        let items = reactive([]);
        for (let i=0; i<this.itemCount; i  ) {
          items.push({
            id: (i 1),
            selected: false,
            name: `Item #${i 1}`
          });
        }
        return items; 
      }
    }
  }
</script>

When I click select/deselect the checkbox, the "selected" text does not get updated. This tells me that I have not bound to the property properly. However, I'm also unsure what I'm doing wrong.

How do I bind a checkbox to the property of an object in an Array in Vue 3?

CodePudding user response:

If you set a breakpoint in getItems(), or output a console.log in there, you will notice every time that you change a checkbox selection, it is getting called. This is because the v-for loop is re-rendered, and it'll call getItems() which will return it a fresh list of items with selected reset to false on everything. The one that was there before is no longer in use by anything.

To fix this, you could only call getItems() from generateItems() for example, and store that array some where - like in data, and change the v-for to iterate that rather than calling the getItems() method.

CodePudding user response:

Steve is right.

Here is a fixed version: Vue SFC Playground.

<template>
  <div>
    <div v-for="(item, index) in items" :key="`item-${index}`">
      <div class="form-check">
        <input type="checkbox" class="form-check-input" :id="`checkbox-${index}`" v-model="item.selected" />
        <label class="form-check-label" :for="`checkbox-${index}`">{{ item.name }} (selected: {{ item.selected }})</label>
      </div>
    </div>

    <button class="btn" @click="generateItems">Generate Items</button>
  </div>
</template>

<script>
  import { reactive } from 'vue';

  export default {
    data() {
      return { items: [] }
    },
    methods: {
      generateItems() {
        this.itemCount = Math.floor(Math.random() * 25)   1;
        this.getRndItems();
      },
      getRndItems() {
        this.items = reactive([]);
        for (let i=0; i<this.itemCount; i  ) {
          this.items.push({
            id: (i 1),
            selected: false,
            name: `Item #${i 1}`
          });
        }
      }      
    }
  }
</script>
  • Related