v-for deletes the wrong element when using array splice despite the use of a unique key

Viewed 104

I am currently using v-for to print out the list of 'parties' elements where each 'party' is represented by a <base-card>. When I use the splice() on the additionalInfoList array which represents the array of 'parties' and I log the output on the console, I see that the correct element has been deleted from the array. However, when I observe the output on the screen, Vue somehow manages to delete either the last element of the array or the adjacent element from the list. Based on various suggestions and research, I have used the unique :key to help Vue, but it still gives me this pernicious bug.

BaseCardList.vue

<template>
    <div>
        <ul>
        <base-card v-once
        @add-parties="updateAdditionalInfoList"
        @delete-party="deleteParty">
            <template v-slot:title>
                    <slot></slot>
                    
                </template>
        </base-card>
        <base-card
        v-for="(data) in additionalInfoListComputed" :key="JSON.stringify(data.id)"
        ref="childComponent"
        @add-parties="updateAdditionalInfoList"
        @delete-party="deleteParty">
            <!-- Wrapper for the `Parties Being Served` component-->
                <template v-slot:title>
                
                    <slot></slot>
                </template>

        </base-card>
        </ul>
        
    </div>
    
    
    
</template>
<script>
export default {
   
    data() {
        return {
            selectedComponent: 'base-card',
            additionalInfoList : [],
            clearForm: false,
            counter: 0
        }
    },
    computed : {
        additionalInfoListComputed(){
            console.log('state changed');
            return this.additionalInfoList;
        }
    },

    methods: {
        updateAdditionalInfoList(additionalInfo){
            this.additionalInfoList.push(additionalInfo);
            
            this.counter++;
            console.log('The length is'+this.additionalInfoList.length);
        },
        deleteParty(resId){
            const resIndex = this.additionalInfoList.findIndex(
                res => res.id === resId
            );
            this.additionalInfoList.splice(resIndex, 1);
            console.log('Index is '+resIndex);
            console.log(this.additionalInfoList);
        }
    }
}
</script>
<style scoped>
ul{
    align-content: left;
    padding: 0
}

</style>  

BaseCard.vue

<template>
--something
</template>    
<script>
    import { EventBus } from './bus.js';
    export default {
        emits:['add-parties','delete-party'],
        
        data() {
            return {
                additionalInfo: 
                    {
                        id: new Date().toISOString(),
                        fullName: '',
                        preAuthorize: '',
                        serviceAddress: ''
                    },
                validation: {
                    fullNameIsValid: true,
                    serviceAddressIsValid: true
                },
                hideAddButton: false,
                formIsValid: true,
                addServiceButtonText: '+ Add Service Notes (Optional)',
                serviceNotes: [],
                showServiceNotes: false,
                showDeleteButton: true,
                enteredServiceNote: '', //service notes addendum
            }
        },
        computed : {
            showServiceNotex(){
                if(!this.showServiceNotes){
                    return '+Add Service Notes (Optional)'
                }else{
                    return '- Remove Service Notes';
                }
            }
        },
        methods: {
            
            setServiceNotes(){
                this.showServiceNotes = !this.showServiceNotes;
            },
            addAnotherParty(){
                this.validateForm();
                if(!this.formIsValid){
                    return;
                }
                
                let emitObj = JSON.parse(JSON.stringify(this.additionalInfo));
                this.$emit('add-parties', emitObj); //event
                this.hideAddButton = !this.hideAddButton;
                console.log(this.hideAddButton);
            },
            deleteParty(){
                this.$emit('delete-party', this.additionalInfo.id);
            },
    
            validateForm(){
                this.formIsValid = true;
    
                if(this.additionalInfo.fullName === ''){
                    this.validation.fullNameIsValid = false;
                    this.formIsValid = false;
                }
                if(this.additionalInfo.serviceAddress === ''){
                    this.validation.serviceAddressIsValid = false;
                    this.formIsValid = false;
                }
            },
            clearValidity(input){
                this.validation[input] = true; 
            },
            clearForm(){
                this.additionalInfo.fullName = '';
                this.additionalInfo.serviceAddress = '';
                this.additionalInfo.preAuthorize = false;
            }
        },
        created(){
            console.log('created');
            console.log(this.hideAddButton);
        }
    }
    </script>

Output Screen enter image description here

2 Answers

You should pass the index to delete method deleteParty

<base-card v-for="(data,index) in additionalInfoListComputed" :key="JSON.stringify(data.id)" ref="childComponent"
  @add-parties="updateAdditionalInfoList" @delete-party="deleteParty(index)">
  <!-- Wrapper for the `Parties Being Served` component-->
  <template v-slot:title>

    <slot></slot>
  </template>
</base-card>

The code generating ids in the child component creates potentially duplicate ids. On a fast enough machine, all of the ids will be the same:

id: new Date().toISOString()

Remove that and use something truly unique like something based on the v-for index.

Related