2

I am trying to move one item in an array, at position x to position 2. The below code works and moves the item to the new position. However, vue doesn't update the DOM.

This is the code that I am using:

export default class LayersPanel extends Vue {
  @ProvideReactive() public layers: any[] = [
    { name: 'Layer 1' }, { name: 'Layer 2' }, { name: 'Layer 3' }, { name: 'Layer 4' }
  ]
  public onDragDrop(evt: DragEvent) {
    let offset = parseInt(evt.dataTransfer.getData('text/plain'))
    this.layers.splice(2, 0, this.layers.splice(offset, 1)[0])
  }
}
<template>
  <div class="layers-panel" @dragover="onDragOver" @drop="onDragDrop">
    <layer v-for="(layer, index) in layers" :key="index" :info="layer" :offset="index"></layer>
  </div>
</template>

I am not sure if this relates to this note in the docs or not:

When you modify an Array by directly setting an index (e.g. arr[0] = val) or modifying its length property. Similarly, Vue.js cannot pickup these changes. Always modify arrays by using an Array instance method, or replacing it entirely. Vue provides a convenience method arr.$set(index, value) which is syntax sugar for arr.splice(index, 1, value).

Supposedly .splice() is a mutation method, so I don't think that it should matter. What am I doing wrong here?

4
  • Most likely layers is not reactive. Is it defined in your data? Commented Nov 20, 2019 at 1:28
  • I am using typescript with decorators: @ProvideReactive() public layers: any[] = [ { name: 'Layer 1' }, { name: 'Layer 2' }, { name: 'Layer 3' }, { name: 'Layer 4' } ] Commented Nov 20, 2019 at 1:31
  • @skirtle If I just remove the element with this.splice(offset, 1) the item gets removed and the DOM updates the item list but not the text Commented Nov 20, 2019 at 1:36
  • This is starting to sound like a problem with your key attribute within the v-for. Difficult to say without seeing the template code. You might want to try dumping out the layer name using {{ name }} within your v-for. I suspect you're using stateful components that aren't reacting to the changes but a {{ name }} should show the latest value. Commented Nov 20, 2019 at 1:41

1 Answer 1

1

A likely source of problems is your choice of key:

<layer v-for="(layer, index) in layers" :key="index" :info="layer" :offset="index"></layer>

Initially the <layer> components will have keys 0, 1, 2 and 3, corresponding to layers Layer 1 through to Layer 4 respectively.

If you then move the layer in position 0 to position 2 then you'll now have them in the order Layer 2, Layer 3, Layer 1, Layer 4. However, the keys are determined by index so Layer 2 will now have a key value of 0.

When Vue tries to pair up the components after a re-render it uses the keys. So the <layer> with a key of 0 used to correspond to Layer 1 but it will now correspond to Layer 2.

That isn't necessarily a problem. Layer 4 is easy: it hasn't moved, so it won't be changed. The other three have moved but they should have their info props updated accordingly.

This is where the problem of stateful components comes in. While the first three components will have their info props updated that won't necessarily have any impact on any other internal state, such as that held by data properties. Those will still retain the value they had for the original info value.

The solution here would generally be to use a more appropriate value for the key. It is unclear from the question what such a value might be in your case. In some cases you may need to add an extra property within each array item just to give it a suitable property.

The important thing is that the keys are distinct and tied to the array items. If the layer's name is a unique identifier then you could use that, e.g. :key="layer.name".

Sign up to request clarification or add additional context in comments.

2 Comments

Okay cool! When would it be appropriate to use the index as a key?
@GetOffMyLawn The key needs to uniquely identify equivalent VNodes (which translate into components and elements) from one rendering to the next. Equivalent components will be updated whereas those with no suitable equivalent will be destroyed or created accordingly. It's really important to understand that: in my experience most developers using Vue don't know what a key does and try to muddle through with over-simplifications like 'never use index'. If you understand what a key does then it should be relatively easy to decide whether a particular choice of value is appropriate.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.