Reputation: 2941
I'm trying to make a list of items that provides an input and a color picker. It should always have an empty element at the end of the list.
Elements are populated with objects that are stored in data
.
Everything is OK, I do a simple method that filters objects with empty value, then add a new one.
But, when the user set empty value on an element that is not the last one:
This is what I do on the parent Vue:
addEmptyElement() {
// now find any empty values
this.values = this.values.filter((v) => v.length);
// and append an empty one
this.values.push("");
},
And the values are use to bind Item component like this:
<Item
v-for="(v, i) in values"
:key="i"
v-bind:text="v"
@valueChanged="onItemChanged"
/>
The "valueChanged" event is emitted by Item and provides the position in the list and value that chaged.
I created a simpler example that you can check here:
https://codesandbox.io/s/vue-component-order-problem-rc4o1?file=/src/App.vue
To give some precision:
this.$.vnode.key
to have find the value to change in parent vue (that's maybe where there is a problem)Maybe I did it wrong, with a bad implementation, so please, tell me what I badly do :)
Thanks !
Upvotes: 1
Views: 1525
Reputation: 37763
I think what you see has more to do with this paragraph in documentation
When Vue is updating a list of elements rendered with
v-for
, by default it uses an "in-place patch" strategy. If the order of the data items has changed, instead of moving the DOM elements to match the order of the items, Vue will patch each element in-place and make sure it reflects what should be rendered at that particular index.
This default mode is efficient, but only suitable when your list render output does not rely on child component state or temporary DOM state (e.g. form input values).
This why using simple index in v-for
as a key
is tricky. It's much better to not rely on index and maintain your own unique id
even it's a bit more work...
Also the usage of this.$.vnode.key
smells...it is not a documented API so you should definitely avoid it and just pass an id
by a prop explicitly
Just after I posted my answer I realized one more thing ...so just to be complete:
This can easily work with v-for
index
as a key. The reason why it didn't worked for you is in your <Item>
component. You pass a text
prop to your Item component. You use the value of the prop for one time initialization of the value
in the data
and use the value
as v-model
for the input
.
What happens when the list is re-rendered ? Vue updates the text
prop for each item in the list, but that update has no effect on the internal component's value
used as v-model
(so it has no effect on the value displayed in the <input>
element)!!
Solution is to rewrite Item
component:
<template>
<div>
<input type="text" v-model="model" />
</div>
</template>
<script>
export default {
props: ["modelValue"],
computed: {
model: {
get() {
return this.modelValue;
},
set(evt) {
this.$emit("update:modelValue", evt);
},
},
},
};
</script>
...and change the App.vue
:
<Item
v-for="(v, i) in values"
:key="i"
:modelValue="v"
@update:modelValue="onItemChanged(i, $event)"
/>
And it just works with the rest of the code unchanged - 2nd example
Upvotes: 2