Element deleted from the array but v-for doesn’t update VUEJS

Guys so I’m trying to develop this rule component which can be spawned by my main component as many times as he wants but the problem is that when I delete an index from the list that tracks the number of rules, the vuejs layout doesn’t update accordingly. What I mean by this is that if I check the array it self it deleted the correct item but when I look at the vue page (HTML) it either doesn’t delete anything or only deletes the last item, and this may be caused by the v-for not updating on list change but I don’t know how to solve this.

NewTask.vue (Parent)

<template>
  <div class="mt-4">
      <div class="container">
          <div class="if-condition container-fluid d-flex flex-row ps-0">
              <span class="text-center me-2 condition rounded">IF</span>
              <select class="form-select form-select-sm me-2 if-select" v-model="if_condition">
                <option value="ALL">ALL</option>
                <option value="ANY">ANY</option>
                <option value="NONE">NONE</option>
              </select>
              <span>of these filters match</span>
          </div>
          <div class="mt-2 ps-3 pt-3 pb-3 border">
            <new-rule v-for="(item, index) in rules"
                      :key="JSON.stringify(index)" v-on:remove-rule="removeRule(index)"
                      :data="item" :index="index" v-on:data-changed="dataChanged"
                      class="mb-2"/>
            <div class="mt-2 add-rule-div">
              <button class="btn add-rule-btn" v-on:click="addRule">+</button>
            </div>
          </div>
      </div>
  </div>
</template>

<script>
import Rule from '@/components/rule'

export default {
    name: "NewTask",
    components: {
      'new-rule': Rule
    },
    data: function () {
      return {
        if_condition: 'ALL',
        rules: []
      }
    },
  methods: {
      dataChanged(data) {
        const rules = this.rules;
        const index = data.index;
        delete data['index'];
        rules.splice(index, 1, data)
        this.rules = rules;
      },
      removeRule(index) {
        const rules = this.rules;
        rules.splice(index, 1)
        this.rules = rules
      },
      addRule() {
        const new_rule = {
          type_input_text: null,
          type_input_show: null,
          rule_input_text: null,
          rule_input_show: null,
        }
        this.rules.push(new_rule)
        console.log(this.rules)
      }
    }
  }
</script>

rule.vue (Child)

<template>
    <div class="if-condition d-flex flex-row">
      <select class="form-select form-select-sm me-2"
              v-on:change="checkTypeSelect" v-model="type_select">
        <option value="HTML">HTML</option>
        <option value="XPATH">XPATH</option>
        <option value="ID">ID</option>
        <option value="CLASS">CLASS</option>
      </select>
      <input v-if="type_input_show" type="text" class="form-control me-2" v-model="type_input_text" v-on:change="dataChanged">
      <select class="form-select form-select-sm me-2"
              v-on:change="checkRuleSelect" v-model="rule_select">
        <option value="CONTAINS">CONTAINS</option>
        <option value="EXISTS">EXISTS</option>
      </select>
      <input v-if="rule_input_show" type="text" class="form-control me-2" v-model="rule_input_text" v-on:change="dataChanged">
      <button class="btn remove-rule-btn pb-0 pt-0 ps-2 pe-2" v-on:click="this.$emit('remove-rule')">-</button>
    </div>
</template>

<script>
export default {
  name: "rule",
  props: {
    data: {
      type: Object,
      required: true
    },
    index: {
      type: Number,
      required: true
    }
  },
  data: function () {
    return {
      type_select: 'HTML',
      type_input_text: '',
      rule_select: 'CONTAINS',
      rule_input_text: '',
      //
      type_input_show: false,
      rule_input_show: true,
    }
  },
  beforeMount() {
    if (this.data.type_select) {
      this.type_select = this.data.type_select
      this.checkTypeSelect()
    }
    if (this.data.type_input_text) {
      this.type_input_text = this.data.type_input_text
    }
    if (this.data.rule_select) {
      this.rule_select = this.data.rule_select
      this.checkRuleSelect()
    }
    if (this.data.rule_input_text) {
      this.rule_input_text = this.data.rule_input_text
    }
  },
  methods: {
    dataChanged() {
      const new_data = {
        index: this.index,
        type_select: this.type_select,
        type_input_text: this.type_input_text,
        rule_select: this.rule_select,
        rule_input_text: this.rule_input_text
      }
      this.$emit('data-changed', new_data)
    },
    checkTypeSelect() {
      const type_select = this.type_select;
      this.type_input_show = type_select !== 'HTML';
      this.dataChanged()
    },
    checkRuleSelect() {
      const rule_select = this.rule_select;
      this.rule_input_show = rule_select !== 'EXISTS';
      this.dataChanged()
    }
  }
}
</script>

Some images to demonstrate the issue:

Array Before Index delete: enter image description here enter image description here

Array After Index delete: enter image description here enter image description here

For further investigation feel free to visit the repo: https://github.com/DEADSEC-SECURITY/Easy-Scraper

THIS IS NOT PUBLICITY, I TRULY NEED HELP

Answer

You are using the index as a key. VueJS updates the DOM based on the change in key. So you have two options:

  • Use some distinct data from each entry or combination as a key.
  • Provide no key because as per Vuejs docs you need not provide a key anymore.