8

I understand from the caveats portion of the Vue docs that updating a value in an array in the following manner will not work:

  this.arr[idx] = newVal

and that one should use splice(). I am using a 2D array to store grid data, and I am having a difficult time updating the value when a cell in the grid is clicked.

Here is my template:

  <tr
      v-for="(row, rowKey, index) in grid"
        :key="rowKey">
        <th
          class="row-col-label"
        >{{rowKey+1}}</th>
        <td
            v-for="(col, colKey, index) in row"
            :key="colKey"
            @click="selectCell(rowKey, colKey)"
            :class="{'selected' : cellSelected(rowKey, colKey)}"
        >
        {{col}}
        </td>
      </tr>

And here is the relevant code for the Vue component:

 created () {
  this.initColHead()
  this.createSpreadSheet()
 },
 data () {
  return {
   selected: '',
   grid: [],
   colHead: [' '],
   isSelected: false
 }
},
methods: {
 initColHead () {
   this.colHead.push(...'ABC'.split(''))
 },
 createSpreadSheet () {
   for (let i = 0; i <= 2; i++) {
     this.grid[i] = []
      for (let j = 0; j <= 2; j++) {
       this.grid[i][j] = false
      }
   }
 },
selectCell (row, col) {
  this.isSelected = true
  console.log(`row ${row} col ${col}`)
  this.grid[row].splice(col, 1, true)
  for (let i = 0; i <= 2; i++) {
    for (let j = 0; j <= 2; j++) {
      console.log(this.grid[i][j])
    }
  }
},
cellSelected (row, col) {
  return (this.grid[row][col] === true)
}
}

So I am attempting to add a true value to the cell that is click at the given row col locations provided in the my selectCell method. However, the data in my grid is not updated to reflect the newly added value. How exactly do I update values in a multidimensional array in Vue?

2 Answers 2

10

One method that works:

selectCell (row, col) {
  //make a copy of the row
  const newRow = this.grid[row].slice(0)
  // update the value
  newRow[col] = true
  // update it in the grid
  this.$set(this.grid, row, newRow)
},

Here is an example.

console.clear()


new Vue({
  el: "#app",
  created() {
    this.initColHead()
    this.createSpreadSheet()
  },
  data() {
    return {
      selected: '',
      grid: [],
      colHead: [' '],
      isSelected: false
    }
  },
  methods: {
    initColHead() {
      this.colHead.push(...'ABC'.split(''))
    },
    createSpreadSheet() {
      for (let i = 0; i <= 2; i++) {
        this.grid[i] = []
        for (let j = 0; j <= 2; j++) {
          this.grid[i][j] = false
        }
      }
    },
    selectCell(row, col) {
      const newRow = this.grid[row].slice(0)
      newRow[col] = true
      this.$set(this.grid, row, newRow)
    },
    cellSelected(row, col) {
      return (this.grid[row][col] === true)
    }
  }
})
.selected {
  background-color: green;
}
<script src="https://unpkg.com/[email protected]/dist/vue.js"></script>
<div id="app">
  <table>
    <tr v-for="(row, rowKey, index) in grid" :key="rowKey">
      <th class="row-col-label">{{rowKey+1}}</th>
      <td v-for="(col, colKey, index) in row" :key="colKey" @click="selectCell(rowKey, colKey)" :class="{'selected' : cellSelected(rowKey, colKey)}">
        {{col}}
      </td>
    </tr>
  </table>
</div>

If I think of something better I'll update later.

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

1 Comment

Thanks!. What I ended up doing is this.grid = this.grid.map(el => el.splice()) in order to update the entire grid after setting the new value and it seems to be working; however, your solution looks a lot better.
4

The difficulty is that you're building the array in a way that Vue does not make its rows reactive. You could build the array and then assign it to the data item as a whole so that Vue would make it reactive, or you can build the array (at last the rows) using push, which will make them reactive. Then you can modify individual elements using splice. Modifying Bert's example:

console.clear()


new Vue({
  el: "#app",
  created() {
    this.initColHead()
    this.createSpreadSheet()
  },
  data() {
    return {
      selected: '',
      grid: [],
      colHead: [' '],
      isSelected: false
    }
  },
  methods: {
    initColHead() {
      this.colHead.push(...'ABC'.split(''))
    },
    createSpreadSheet() {
      for (var i = 0; i <= 2; i++) {
        this.grid.push([]);
        for (var j = 0; j <= 2; j++) {
          this.grid[i].push(false);
        }
      }
    },
    selectCell(row, col) {
      this.grid[row].splice(col, 1, true);
    },
    cellSelected(row, col) {
      return (this.grid[row][col] === true)
    }
  }
})
.selected {
  background-color: green;
}
<script src="https://unpkg.com/[email protected]/dist/vue.js"></script>
<div id="app">
  <table>
    <tr v-for="(row, rowKey, index) in grid" :key="rowKey">
      <th class="row-col-label">{{rowKey+1}}</th>
      <td v-for="(col, colKey, index) in row" :key="colKey" @click="selectCell(rowKey, colKey)" :class="{'selected' : cellSelected(rowKey, colKey)}">
        {{col}}
      </td>
    </tr>
  </table>
</div>

3 Comments

Good catch! I didn't notice the implication of how the grid was built.
Very nice. Can you explain why using push works differently?
@MahmudAdam For the same reason that splice is recommended: it is one of the mutation methods that Vue re-wrote for reactive arrays.

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.