Jeremy Dicaire
Jeremy Dicaire

Reputation: 4905

How do I notify VueJS/Vuex of an value change in an array?

when I update a value in an array of the store, the interface doesn't reflect the change.

Goal: I'm trying to display a simple minesweeper grid. Once I click on a cell, the object attach to that cell should update isRevealed to true to say is was revealed and Vuejs should add the class dug to that cell.

What's working: isRevealed is switched to true.

What was working: using only props everything was working, but trying to learn VueJS and including Vuex store the UI doesn't update anymore with the array.

Structure:

App shows a grid and grid shows multiple cell.

store.js for Vuex

import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

export const store = new Vuex.Store({
    state: {
        GameGrid: [],
    },
mutations: {
    revealCell(state, cell) {
        state.GameGrid[cell.y][cell.x].isRevealed = true;
        console.log("revealCell ", cell);


    },
});

GameGrid contain the following: 10 arrays of 10 objects:

{
    isRevealed: false,
    x: 0,
    y: 0
    ...
}

grid.vue, nothing special, display every cell

<template>

    <div class="grid">
        <div v-for="row in $store.state.GameGrid" class="row">
            <app-cell v-for="col in row" :cell="col"></app-cell>
        </div>
    </div>

</template>

cell.vue:

<template>
    <div
        @click="dig"
        :class="{dug: cell.isRevealed, dd: cell.debug}">
        <span class="text" v-html="cell.display"></span>
        <span class="small">{‌{ cell.code }}</span>
    </div>
</template>
<script>
    export default {
        props: {
            cell: {
                type: Object
            },
        },
        data: function() {
           return {
               display: null,
           }
        },
        methods: {
            dig() {
                this.$store.commit('revealCell', this.cell);
            },
    }
</script>

Edit accoridng to pirs answer:

Grid.vue file

<template>

    <div class="grid">
        <div v-for="row in gameGridAlias" class="row">
            <app-cell v-for="col in row" :cell="col"></app-cell>
        </div>
    </div>

</template>

<script>
    import { mapState } from 'vuex';
    import Cell from './Cell.vue';
    export default {
        components: {
            'app-cell': Cell,
        },
        data: {
                gameGridAlias: []
        },
        methods: {

        },
        computed: mapState({
            gameGridAlias: state => state.GameGrid
        })

    }

</script>

Note that I get The "data" option should be a function that returns a per-instance value in component definitions. error since data doesn't return a function.

My functions to set isRevealed are on the cell.vue though. I'm still seeing no UI updates (except when I'm saving?). Cells with this.cell.isRevealed = true confirmed with console.log but no change in classes.

Here is how I'm populating GameGrid

This function is calling when the user press a button on the hmm, home page? App.vue file.

     generateGrid() {
          console.log("generateGrid");
          //Generate empty play grid
          for(let y = 0; y < this.$store.state.GameGridHeight; y++) {
              this.$store.state.GameGrid[y] = new Array();
              for(let x = 0; x < this.$store.state.GameGridWidth; x++) {
                  this.$store.state.GameGrid[y][x] = {
                      content: 'Empty',
                      code: 0,
                      isRevealed: false,
                      x: x,
                      y: y,
                      debug: false,
                  };
              }
          }
      }, //End generateGrid

Upvotes: 2

Views: 2310

Answers (2)

Stan Sokolov
Stan Sokolov

Reputation: 2260

state.GameGrid[cell.y][cell.x].isRevealed = true;
add this
state.GameGrid =  Array.from(state.GameGrid); //force reflection by cloning array

It is not the most efficient solution but it only one that works for me

Upvotes: 0

pirs
pirs

Reputation: 2463

In your grid.vue, you should use computed and mapState to update the props

It would be like:

import { mapState } from 'vuex'

// ...

<div v-for="row in gameGridAlias" class="row">

// ...

computed: mapState({
  gameGridAlias: state => state.GameGrid
})

More : https://vuex.vuejs.org/en/state.html#the-mapstate-helper

*Note: mapState is optional, but you can handle multiple stores with it and it's robust, i use it for all cases personally.

Edit: Don't use this.$store.state.GameGrid[y] or another this.$store.state vars but only mapState aliases to make it work correctly

Upvotes: 2

Related Questions