Search code examples
javascriptmultidimensional-arrayforeach

TypeError: Cannot read properties of undefined (reading '_matrix')


I want to write my position of figures on chess board (checkers) from Node List of tiles. I define a 2-dimensional array and fill it with 0. And I can freely change the value, if I use this._matrix[0][1] = 1; right at the start of method. However, when it comes to changing it inside of a forEach array, I get this type of error.

how it is supposed to finally be

My code:

class Game{
    _matrix = Array(8).fill().map(() => Array(8).fill(0));
    _board = document.querySelectorAll("div.container>div"); // all tiles

    constructor(blackTiles){
        this.blackTiles = blackTiles;
    }
    
    fillMatrix(){
        console.log(this._matrix[0][1]) // 0
        this._matrix[0][1] = 1 // no errors
        console.log(this._matrix[0][1]) // 1

        this._board.forEach(function(element, i){
            if(i < 8){
                if(element.classList.contains("infest")){ //tiles, that have figures, have class "infest"
                    try{
                        let row = i / 8;
                        let col = i % 8;
                        this._matrix[parseInt(row)][parseInt(col)] = 1; //error
                    } catch(Error) { console.error(Error); }
                }
                i++;
            }
        });

        return this._matrix;
    }
}

Full error:

TypeError: Cannot read properties of undefined (reading '_matrix')
    at script.js:41:30
    at NodeList.forEach (<anonymous>)
    at Game.fillMatrix (script.js:35:21)
    at window.onload (script.js:102:22)

Solution

  • As you correctly found, the problem is on that specific line.

    That is because inside a function, (defined with function(){}), this refers to the function itself, not the containing context.

    You have three options at this point.

    1. Make and use a reference to this (old school :)):
    const _this = this;
    list.forEach(function(){
        _this.something;
    })
    
    1. Bind the function to the current object:
    list.forEach((function(){
        this.something;
    }).bind(this));
    
    1. Use an arrow function; it will be bound to the caller automatically:
    list.forEach(() => {
        this.something;
    });
    
    1. Bonus point: if you are using classes, you could also have a private lambda property do that as well:
    _handleSomething = () => {
        this.something;
    }
    
    list.forEach(this._handleSomething);
    

    I'd go for option 3.

    Option 4 is useful when your function is an event handler that you need to remove afterwards.