Search code examples
javascriptvue.jsbootstrap-tokenfield

V-model doesn't work when using Bootstrap Tokenfield


I create dynamic component using Vue and Bootstrap Tokenfield. But v-model doesn't work in this case.

Assume, I have an array below:

index variant_options
1 aaa
2 sss

When I remove index 1, the result of index 1 should be "sss" but still "aaa"

<div class="card" v-for="(variant, index) in form.variants" :key="index">
<div class="card-body"> <span class="float-right" style="cursor: pointer" @click="deleteVariant(index)">
                                                            X
                                                        </span>
    <div class="row">
        <div class="col-md-4">
            <label for="weight">Variant Type {{ index + 1 }} </label>
            <div class="input-group">
                <input type="text" id="variant_type" class="form-control" v-model="
                                                                            variant.variant_type
                                                                        " @keyup="tokenField()" placeholder="Input variant type. E.g: Color" name="name" required autofocus /> </div>
        </div>
        <div class="col-md-8">
            <label for="weight">Variant Options {{ index + 1 }}</label>
            <div class="input-group">
                <input type="text" id="variant_options" autofocus="true" v-model="
                                                                            variant.variant_options
                                                                        " @mouseover="
                                                                            tokenField()
                                                                        " placeholder="Input variant options. E.g: Blue, Brown," class="
                                                                            form-control
                                                                            variant_options
                                                                        " /> </div>
data() {
    return {
        form: new Form({
            variants: [
                {
                    variant_type: '',
                    variant_options: '',
                },
            ],
        }),
    };
},
methods: {
    tokenField() {
        $('.variant_options').tokenfield({
            showAutocompleteOnFocus: true,
        });
    },
    addVariant() {
        if (this.form.variants.length <= 1) {
            this.form.variants.push({
                variant_type: '',
                variant_options: '',
            });
        } else {
            this.error = 'You can only add 2 type of varians';
            $('#errMsg').show();
        }
    },
    deleteVariant(index) {
        this.form.variants.splice(index, 1);
        $('#errMsg').hide();
    },
}, // methods:

Solution

  • When rendering list using v-for do not use index variable from v-for as :key, especially not when loop content contains any <input> element and/or you are adding/removing/sorting items...

    See the docs - Maintaining State

    Note that the docs does not mention or discourage using index variable as :key in any way. But if you think about it, using index is indeed same as not using :key at all. Because role of :key is to establish relationship (identity) between each item used in the loop and the DOM that is generated for it. This is something index can't do...

    key should be stable (not change over time) and unique over all the items in the list. Sometime the data already contain such item (for example when the data comes from server/database). If there is no data property with above mentioned features (as in your case where both variant_type and variant_options are editable by the user), just generate your own artificial key. There are multiple ways to generate unique id in JS

    Example using "running index":

    data() {
        return {
            nextId: 1,
            form: new Form({
                variants: [
                    {
                        id: 0,
                        variant_type: '',
                        variant_options: '',
                    },
                ],
            }),
        };
    },
    methods: {
        addVariant() {
            if (this.form.variants.length <= 1) {
                this.form.variants.push({
                    id: this.nextId++,
                    variant_type: '',
                    variant_options: '',
                });
            } else {
                this.error = 'You can only add 2 type of varians';
                $('#errMsg').show();
            }
        },
    }
    

    ...and use :key="variant.id" in the template