Search code examples
vue.jsvue-componentvuetify.jsv-forvue-reactivity

Why does Vue render component incorrectly?


Below is an example of an error.

Reproduce the problem: Add 3 items and remove the second.

Everything inside is deleted correctly. This can be seen from the rendering of the text below. But the component is not displayed correctly. Why? Is this a bug?

I tried to use an additional property for conditional rendering, tried to overwrite references to an array of elements and inside the array - no results.

Vue.component('selected-material', {
  props: [
    'value'
  ],
  template: `
    <div>
      <v-autocomplete
        v-model="local"
        :items="materials"
        item-text="name"
        return-object
        autocomplete="new-password"
        @change="input"
      />
    </div>
  `,
  data() {
    return {
      local: this.value,
      materials: [{
          id: 1,
          name: 'mat-1',
          q: 1
        },
        {
          id: 2,
          name: 'mat-2',
          q: 1
        },
      ],
    };
  },
  methods: {
    input() {
      this.$emit('input', this.local);
    },
  },
})

Vue.component('add-board', {
  props: [
    'value'
  ],
  template: `
    <div>
      <v-container fluid class="my-0 py-0">
        <v-row class="my-0 py-0">
          <v-col class="my-0 py-0">
            <selected-material
              v-model="local.material"
            />
          </v-col>
          <v-col class="my-0 py-0">
            <v-text-field
              class="my-0 py-0"
              v-model="local.material.q"
            />
          </v-col>
          <v-col class="my-0 py-0">
            <v-row class="my-0 py-0">
              <v-col class="my-0 py-0">
                <v-btn
                  class="my-0 py-0"
                  color="success"
                  icon
                  @click="append"
                >
                  <v-icon>mdi-plus</v-icon>
                </v-btn>
              </v-col>
              <v-col class="my-0 py-0">
                <v-btn
                  class="my-0 py-0"
                  color="error"
                  icon
                  @click="remove"
                >
                  <v-icon>mdi-minus</v-icon>
                </v-btn>
              </v-col>
            </v-row>
          </v-col>
        </v-row>
      </v-container>
    </div>
  `,
  data() {
    return {
      local: this.value,
    };
  },
  methods: {
    input() {
      this.$emit('input', this.local);
    },
    append() {
      this.$emit('append');
    },
    remove() {
      this.$emit('remove', this.local.id);
    },
  },
})

new Vue({
  el: '#app',
  vuetify: new Vuetify(),
  data() {
    return {
      boards: [],
    };
  },
  mounted() {
    this.append();
  },
  methods: {
    append() {
      this.boards.push({
        id: Date.now(),
        material: {
          id: 1,
          name: 'mat-1',
          q: 1
        },
      });
    },
    remove(id) {
      if (this.boards.length !== 1) {
        const index = this.boards.findIndex(board => board.id === id);

        this.boards.splice(index, 1);
      }
    },
  },
})
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/@mdi/font@4.x/css/materialdesignicons.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/@mdi/font@5.x/css/materialdesignicons.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=Material+Icons" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/vue@2.x/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.js"></script>

<div id="app">
  <v-app>
    <v-main>
      <v-row v-for="(board, index) in boards" :key="index">
        <v-col>
          <add-board :key="index" v-model="boards[index]" @append="append" @remove="remove" />
        </v-col>
      </v-row>

      <v-row v-for="(board, index) in boards" :key="`_${index}`">
        <v-col>
          {{ board.id }} | {{ board.material.q }}
        </v-col>
      </v-row>
    </v-main>
  </v-app>
</div>

UPD:

After replaced with ID:

enter image description here


Solution

  • When removing items from a v-for list, it's important to use a key that's unique to each item if you don't want the DOM to be reused. If you use index and remove an item, the next item takes its index, so Vue reuses the DOM of the removed item.

    Use the id as a key, since that seems to be unique:

    <v-row v-for="(board, index) in boards" :key="board.id">
    

    Also, check the v-model on the <v-text-field>, it seems like you might intend for it to use local.material.q rather than local.q:

    <v-text-field
      class="my-0 py-0"
      v-model="local.material.q"
    />