Why does vuejs replicates its v-model data when the v-model is referenced within a computed property?

216 Views Asked by At

In the following code:

JS

const App = {
  template: '#app-template',
  data: () => ({
    selected: [],
    items: Array.from({length: 50}, (x, i) => i+1).map(i => ({
      id: i ,
      name: `Item ${i}`,
      subtitle: `Subtitle ${i}`
    }))
  }),
  computed: {
    parsedItems() {
      this.selected;
      return this.items.map(item => ({
        someValue: 3,
        ...item
      }));
    }
  }
}


new Vue({
  vuetify: new Vuetify(),
  render: h => h(App)
}).$mount('#app')

HTML

<script type="text/x-template" id="app-template">
  <v-app>
    {{selected}}
    <v-container>

        <v-virtual-scroll
          :items="parsedItems"
          :item-height="65"
          height="500"
        >
          <template v-slot="{ item, index }">
           <v-list-item-group
             v-model="selected"
             multiple
           >
            <v-list-item :key="item.id" :value="item">
              <v-list-item-action>
                <v-checkbox
                  :input-value="selected.includes(item.id)"
                  color="primary"
                />
              </v-list-item-action>
              <v-list-item-content>
                <v-list-item-title>
                  Index: {{ index }} {{ item.name }}
                </v-list-item-title>
                <v-list-item-subtitle>
                  {{ item.subtitle }}
                </v-list-item-subtitle>
              </v-list-item-content>
            </v-list-item>
           </v-list-item-group>
          </template>
        </v-virtual-scroll>
    </v-container>
  </v-app>
</script>

<div id="app"></div>

When either of the checkboxes i checked or unchecked - the selected v-model always adds up another instance though it previously contains one already.

Removing this.selected; (line 16 in the Codepen below) fixes the issue.

I suspect that this.selected is somehow dereferences its own values and then can't validate the appearance of previously selected items.

Here's a Codepen with the issue at hand: https://codepen.io/MichaelKatz/pen/vYXXdgb

In my real-world scenario, I need to filter and manipulate items within the list, according to previously made selections (i.e remove / re-add items). I do it by using a computed item property which derives its content from a previously selected items, from the selected v-model and my current solution will require me to JSON.stringify all of my objects, essentially making them value-based strings to keep things in check.

3

There are 3 best solutions below

0
On BEST ANSWER

It seems like accessing a v-model while filtering the items it refers to, creates a de-reference of the objects within it.

The best solution I could come up with was adding an additional computed property which will contain the logic involving this.selected.

It has indeed solved the issue for me.

  computed: {
    parsedItems() {
      return this.items.map(item => ({
        someValue: 3,
        ...item
      }));
    },
    filteredItems() { // adding another computed property while using this.selected
      this.selected;
      return this.parsedItems;
    }
  }
}
0
On

From my perspective, the problem is you have used the multiple props, which will allow multiple selections.

      <template v-slot="{ item, index }">
       <v-list-item-group
         v-model="selected"
         multiple
       >

Simply removing it will solve your problem.

1
On

The v-model does not seem to work with Objects

<v-list-item :key="item.id" :value="item">    <!-- change this -->
<v-list-item :key="item.id" :value="item.id"> <!-- into this   -->

And create a new computed property to "hydrate" those ids:

selectedItems() {
  return this.selected.map(id => this.parsedItems.find(x => x.id === id))
}

Updated Codepen