0

i'm trying re-organised a list of data. I have given each li a unique key, but still, no luck!

I have had this working before exactly like below, think i'm cracking up!

let app = new Vue({
  el: '#app',
  data: {
    list: [
      { value: 'item 1', id: '43234r' },
      { value: 'item 2', id: '32rsdf' },
      { value: 'item 3', id: 'fdsfsdf' },
      { value: 'item 4', id: 'sdfg543' }
    ]
  },
  methods: {
    randomise: function() {
      let input = this.list;
     
      for (let i = input.length-1; i >=0; i--) {

          let randomIndex = Math.floor(Math.random()*(i+1)); 
          let itemAtIndex = input[randomIndex]; 

          input[randomIndex] = input[i]; 
          input[i] = itemAtIndex;
      }
      this.list = input;
     
    }
  }
});
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
  
  <ul>
    <li v-for="item in list" :key="item.id">{{ item.value }}</li>
  </ul>
  <a href="javascript:void(0)" v-on:click="randomise()">Randomize</a>
  
</div>

Edit:

Thanks for the answers, to be honest the example I provided may not have been the best for my actual issue I was trying to solve. I think I may have found the cause of my issue.

I'm basically using a similar logic as above, except i'm moving an array of objects around based on drag and drop, this works fine with normal HTML.

However, i'm using my drag and drop component somewhere else, which contains ANOTHER component and this is where things seem to fall apart...

Would having a component within another component stop Vue from re-rendering when an item is moved within it's data?

Below is my DraggableBase component, which I extend from:

<script>
    export default {
        data: function() {
            return {
                dragStartClass: 'drag-start',
                dragEnterClass: 'drag-enter',
                activeIndex: null
            }
        },
        methods: {

            setClass: function(dragStatus) {
                switch (dragStatus) {
                    case 0:
                        return null;
                    case 1:
                        return this.dragStartClass;
                    case 2:
                        return this.dragEnterClass;
                    case 3:
                        return this.dragStartClass + ' ' + this.dragEnterClass;
                }
            },
            onDragStart: function(event, index) {

                event.stopPropagation();

                this.activeIndex = index;
                this.data.data[index].dragCurrent = true;

                this.data.data[index].dragStatus = 3;

            },
            onDragLeave: function(event, index) {

                this.data.data[index].counter--;

                if (this.data.data[index].counter !== 0) return;

                if (this.data.data[index].dragStatus === 3) {
                    this.data.data[index].dragStatus = 1;
                    return;
                }
                this.data.data[index].dragStatus = 0;
            },
            onDragEnter: function(event, index) {

                this.data.data[index].counter++;

                if (this.data.data[index].dragCurrent) {
                    this.data.data[index].dragStatus = 3;
                    return;
                }
                this.data.data[index].dragStatus = 2;
            },
            onDragOver: function(event, index) {
                if (event.preventDefault) {
                    event.preventDefault();
                }
                event.dataTransfer.dropEffect = 'move';
                return false;
            },
            onDragEnd: function(event, index) {
                this.data.data[index].dragStatus = 0;
                this.data.data[index].dragCurrent = false;
            },
            onDrop: function(event, index) {

                if (event.stopPropagation) {
                    event.stopPropagation();
                }

                if (this.activeIndex !== index) {
                    this.data.data = this.array_move(this.data.data, this.activeIndex, index);
                }

                for (let index in this.data.data) {
                    if (!this.data.data.hasOwnProperty(index)) continue;
                    this.data.data[index].dragStatus = 0;
                    this.data.data[index].counter = 0;
                    this.data.data[index].dragCurrent = false;
                }

                return false;
            },
            array_move: function(arr, old_index, new_index) {
                if (new_index >= arr.length) {
                    let k = new_index - arr.length + 1;
                    while (k--) {
                        arr.push(undefined);
                    }
                }
                arr.splice(new_index, 0, arr.splice(old_index, 1)[0]);
                return arr; // for testing
            }
        }
    }
</script>

Edit 2

Figured it out! Using the loop index worked fine before, however this doesn't appear to be the case this time!

I changed the v-bind:key to use the database ID and this solved the issue!

4 Answers 4

2

There are some Caveats with arrays

Due to limitations in JavaScript, Vue cannot detect the following changes to an array:

  • When you directly set an item with the index, e.g. vm.items[indexOfItem] = newValue

  • When you modify the length of the array, e.g. vm.items.length = newLength


To overcome caveat 1, both of the following will accomplish the same as vm.items[indexOfItem] = newValue, but will also trigger state updates in the reactivity system:

Vue.set(vm.items, indexOfItem, newValue)

Or in your case

randomise: function() {
      let input = this.list;
     
      for (let i = input.length-1; i >=0; i--) {

          let randomIndex = Math.floor(Math.random()*(i+1)); 
          let itemAtIndex = input[randomIndex]; 
          
          Vue.set(input, randomIndex, input[i]); 
          Vue.set(input, i, itemAtIndex);
      }
      this.list = input;
     
    }
Sign up to request clarification or add additional context in comments.

Comments

1

Here is an working example: Randomize items fiddle

Basically I changed the logic of your randomize function to this:

randomize() {
  let new_list = []
  const old_list = [...this.list] //we don't need to copy, but just to be sure for any future update

  while (new_list.length < 4) {
    const new_item = old_list[this.get_random_number()]
    const exists = new_list.findIndex(item => item.id === new_item.id)
    if (!~exists) { //if the new item does not exists in the new randomize list add it
      new_list.push(new_item)
    }
  }
  this.list = new_list //update the old list with the new one   
},
get_random_number() { //returns a random number from 0 to 3
    return Math.floor(Math.random() * 4)
}

Comments

0
randomise: function() {   let input = this.list;
    for (let i = input.length-1; i >=0; i--) {

      let randomIndex = Math.floor(Math.random()*(i+1)); 
      let itemAtIndex = this.list[randomIndex]; 
  
      Vue.set(this.list,randomIndex,this.list[i])
      this.list[randomIndex] = this.list[i]; 
      this.list[i] = itemAtIndex;
      }   this.list = input;
      }

Array change detection is a bit tricky in Vue. Most of the in place array methods are working as expected (i.e. doing a splice in your $data.names array would work), but assigining values directly (i.e. $data.names[0] = 'Joe') would not update the reactively rendered components. Depending on how you process the server side results you might need to think about these options described in the in vue documentation: Array Change Detection.

Some ideas to explore:

using the v-bind:key="some_id" to have better using the push to add new elements using Vue.set(example1.items, indexOfItem, newValue) (also mentioned by Artokun)

Source

Note that it works but im busy so i cant optimize it, but its a little bit too complicted, i Edit it further tomorrow.

Comments

0

Since Vue.js has some caveats detecting array modification as other answers to this question highlight, you can just make a shallow copy of array before randomazing it:

randomise: function() {
  // make shallow copy
  let input = this.list.map(function(item) {
     return item;
  });
 
  for (let i = input.length-1; i >=0; i--) {

      let randomIndex = Math.floor(Math.random()*(i+1)); 
      let itemAtIndex = input[randomIndex]; 

      input[randomIndex] = input[i]; 
      input[i] = itemAtIndex;
  }

  this.list = input;
 
}

Comments

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.