Search code examples
javascriptvue.jsvuex

Click handler doesn't work without using a specific vuex state in the template


A very confusing bug's creeping into my vue component; if I add line 5 from the code below, it works as expected, if I remove it, the click method for each board-slot element stops changing the boardSlots object.

What does using {{currentPlayer}} have to do with this? I'm not using it anywhere else. It doesn't necessarily need to be printed, just needs to be somewhere in the template's definition, so a random element with a class binding of currentPlayer also miraculously makes it work. Is there something about Vuex's architecture that I'm missing?

<template>
    <div id="board-container">

    //THIS LINE 
    {{currentPlayer}}

        <div id="board">
            <div class="board-row" v-for="i in boardSlots">
                <div class="board-slot"
                    v-for="boardSlot in i"
                    :class="[{checked: (boardSlot.owner != 0)}, 'ownedBy-player-' + boardSlot.owner]"
                    @click="checkSlot(boardSlot.row, boardSlot.col)">
                </div>
            </div>
        </div>
    </div>
</template>

<script>

import { mapState, mapActions } from 'vuex';

export default{
    created(){
        for(var i = 0; i < 6; i++){
            this.boardSlots[i] = [];
        }

        var row = 0;
        var col = 0;

        for(var i = 1; i <= 42; i++){
            this.boardSlots[row].push({
                owner: 0,
                hover: false,
                row: row,
                col: col,
            });

            col++;

            if (col == 7) {
                col = 0;
                if (row < 5) {
                    row++;                  
                }
            }

        }
    },

    data(){
        return {
            boardSlots: [],
        }
    },

    computed: {
        ...mapState([
            'currentPlayer',
        ]),
    },

    methods: {
        ...mapActions([
            'swapToNextPlayer',
        ]),

        checkSlot(row, col){
            if (this.boardSlots[row][col].owner == 0) {
                this.boardSlots[row][col].owner = 1;
                this.swapToNextPlayer();
            }
        }
    }
}


</script>

Solution

  • I'm not sure what {{currentPlayer}} specifically has to do with it, but any time you modify the component's state and the view doesn't update, then (assuming there is no error during rendering) it usually means either:

    1. You are mutating the state in a way which Vue cannot observe.
    2. Your state is not reactive, which can happen as a result of the previous point.

    This is a red flag:

    this.boardSlots[i] = [];
    

    Vue cannot detect assignments to elements of an array like that (read Array Change Detection). You need to use splice() or push() for Vue to know about it. Or alternatively you can construct the boardSlots array in a local variable and then assign it to this.boardSlots at the end:

    const boardSlots = [];
    
    // Initialize boardSlots ...
    
    this.boardSlots = boardSlots;
    

    Also you're rendering the rows and slots in a weird way; you shouldn't be mutating any state during rendering, local or otherwise (--i). Just do this:

    <div class="board-row" v-for="slots of boardSlots">
        <div class="board-slot"
            v-for="slot of slots"
            :class="[{checked: (slot.owner != 0)}, 'ownedBy-player-' + slot.owner]"
            @click="checkSlot(slot.row, slot.col)">
        </div>
    </div>
    

    But this may not have anything to do with your reactivity issue.