Home > Software engineering >  Why isn't Vue prop resetting after re-rendering component?
Why isn't Vue prop resetting after re-rendering component?

Time:09-09

I am trying to create a board that can change size at the click of a button. The board has width and height attributes that determine how many cells wide and tall it is. When I change these width and height attributes, the board re-renders to the proper size, but its props (namely a coords prop that is an [x,y] array of the position of the cell on the board) does not update properly.

When the page first renders (with a board width and height of 10) and I console.log the cells of the board, the top-left cell has the coordinates of [0,0], which is correct. screenshot.

However, after resetting the width and height to 5 each, the top-left cell has coords of [4,0], and I don't understand why. screenshot.

Here is my code for App.vue:

<template>
  <button @click="resetBoard()">reset game with new board dimensions</button>
  <div :style="{ width: boardWidth, height: boardHeight }">
    <div v-for="y in height" :key="y">
      <BoardCell
        v-for="x in width"
        ref="cells"
        :coords="[x - 1, y - 1]"
        :key="y * width   x"
      />
    </div>
  </div>
  <button @click="logCells">log cells to console</button>
</template>

<script>
import BoardCell from "./components/BoardCell.vue";

export default {
  name: "App",
  components: { BoardCell },
  data() {
    return {
      width: 10,
      height: 10,
    };
  },
  computed: {
    boardWidth() {
      return (this.width * 40).toString()   "px";
    },
    boardHeight() {
      return (this.height * 40).toString()   "px";
    },
  },
  methods: {
    resetBoard() {
      this.width = 5;
      this.height = 5;
    },
    logCells(){
      console.log('this.$refs.cells:')
      console.log(this.$refs.cells)
    }
  },
};
</script>

And here is my code for the cell component:

<template>
  <div >
  </div>
</template>

<script>
export default {
  props: ['coords']
}
</script>

<style scoped>
.cell {
  width: 40px;
  height: 40px;
  float: left;
  line-height: 0px;
  background-color: limegreen;
  border: 1px green solid;
  box-sizing: border-box;
}
</style>

CodePudding user response:

I think the issue may be connected to the key :key="y * width x". It is probably not unique.

When you change the size of the board the Vue renderer tries to reuse the elements from the previous board, and because the previous board has the elements with the same keys you may see old elements in your new board.

For example:

  • you had a board 10x10, then key for element [1][1] is :key="11"
  • for a new board 5x5, the key for element [2][1] is :key="11" as well, so Vue renderer will reuse the old element here.

To avoid it I think you should try to create keys that will be unique for different boards

An example

<div v-for="y in height" :key="`${height}x${width}[${y}]`">
  <BoardCell
    v-for="x in width"
    ref="cells"
    :coords="[x - 1, y - 1]"
    :key="`${height}x${width}[${y}][${x}]`"
  />
</div>

In that example you going to have keys like 10x10[1][1] or 5x5[2][1].

CodePudding user response:

Actually, the ref you're console logging is not the top left cell but the top right. The order of the $refs array used in a v-for is not guaranteed to match the source array. According to the docs:

$refs are only populated after the component has been rendered, and they are not reactive. It is only meant as an escape hatch for direct child manipulation - you should avoid accessing $refs from within templates or computed properties.

I threw your code in a sandbox and using Vue DevTools I verified the coordinates of the top left cell are correct using the component inspector which you can see here (I changed the color to make the cell highlighting easier to see)

If you really need to keep track of the cells and their proper order you could make an array of "Cell" objects. It looks like you could benefit from this where each object could perhaps store it's own coordinate.

  • Related