Search code examples
vue.jsvuejs3

List of components not rendered correctly after deleting one child


I've a list of objects that are loaded into a for-loop of child components. For each child, there is a button that allow me to remove the selected child.

The deleted child is deleted correctly from the list but, when rendered, only the last child is removed.

I don't know what I'm doing wrong.

Here is an example : enter image description here

The result : enter image description here

Here is the code from the parent :

<tbody>
  <route-form-row
    v-for="(route, index) in form.current_routes"
    :key="index"
    :organizations="organizations"
    :route="route"
    :index="index"
    :completed="form.completed"
    @onUpdateOrganization="updateCurrentRouteOrganization"
    @onUpdateNumberOfRoutes="updateCurrentRouteNumberOfRoutes"
    @onRemoveRoute="removeCurrentRoute"
  ></route-form-row>
</tbody>

Here is how I remove the child item:

removeCurrentRoute(index) {
  this.form.current_routes.splice(index, 1);
}

Here is the child component:

<template>
  <tr>
    <td>
      <select v-model="organization" :class="['form-select', {'is-invalid': organizationError}]" @change.prevent="updateOrganization()">
        <option value="">------</option>
        <option v-for="org in organizations" :key="org.id" :value="org" :selected="organization.id === org.id">{{ org.name }}</option>
      </select>
      <div v-for="error of this.v$.organization.$errors" :key="error.$uid" :class="[{'invalid-feedback': organizationError}]">{{ error.$message }}</div>
    </td>
    <td class="col-2">
      <input type="number" v-model="number_of_routes" :class="['form-control', {'is-invalid': numberOfRoutesError}]" step="1" min="0" @keyup="updateNumberOfRoutes">
      <div v-for="error of this.v$.number_of_routes.$errors" :key="error.$uid" :class="[{'invalid-feedback': numberOfRoutesError}]">{{ error.$message }}</div>
    </td>
    <td class="text-end">
      <div>
        <a href="#" @click.prevent="removeRoute()">
          <font-awesome-icon :icon="['far', 'trash-can']" />
        </a>
      </div>
    </td>
  </tr>
</template>

<script>
import { useVuelidate } from '@vuelidate/core'
import { requiredIf, helpers, minValue } from "@vuelidate/validators"

export default {
  name: 'RouteFormRow',
  props: {
    index: Number,
    route: Object,
    organizations: Array,
    completed: Boolean
  },
  setup() {
    return { v$: useVuelidate() };
  },
  data() {
    return {
      organization: this.route.organization,
      number_of_routes: this.route.number_of_routes,
    }
  },
  validations() {
    return {
      organization: { required: helpers.withMessage('Ce champ est obligatoire', requiredIf(this.completed)) },
      number_of_routes: {
        required: helpers.withMessage('Ce champ est obligatoire', requiredIf(this.completed)),
        minValue: helpers.withMessage('Assurez-vous que cette valeur est supérieure ou égale à 0.', minValue(0)),
      }
    }
  },
  computed: {
    organizationError () {return this.v$.organization.$errors.length > 0},
    numberOfRoutesError () {return this.v$.number_of_routes.$errors.length > 0},
  },
  methods: {
    updateOrganization() {
      this.$emit('onUpdateOrganization', this.index, this.organization);
    },
    updateNumberOfRoutes() {
      this.$emit('onUpdateNumberOfRoutes', this.index, this.number_of_routes)
    },
    removeRoute() {
      this.$emit('onRemoveRoute', this.index);
    }
  }
}
</script>

Here is the result when data is loaded and after I clicked on "delete" : enter image description here


Solution

  • I found the problem.

    As @EstusFlask mentioned, I should not use the index as key.

    After further investigation, I replaced :key="index" with :key="route".

    Now it works fine.