Search code examples
javascriptfunctionthistic-tac-toe

this.id is undefined. I get this error while coding the classic tic tac toe


I am trying to call the tile id with this. id from an EventListener in another function.

Like this:

const setGame = () => {
    board = [
        [' ', ' ', ' '],
        [' ', ' ', ' '],
        [' ', ' ', ' ']
    ]

    for(let r = 0 ; r < 3; r++) {
        for(let c = 0 ; c < 3; c++) {
            let tile = document.createElement("div")
            tile.id = r.toString() + "-" + c.toString();
            tile.classList.add("col-4", "box", "d-flex", "justify-content-center", "align-items-center");
            
            tile.addEventListener("click", setTile);
            document.getElementById("board").append(tile);
        }
    }
}

const setTile = () => {
    if (gameOver) {
        return;
    }
    let coords = this.id.split("-")  //splits the id string "1-1" into an array["1", "1"]
    let r = parseInt(coords[0]);
    let c = parseInt(coords[1]);

    board[r][c] = currPlayer;
    this.innerText = currPlayer;

}

I am not an expert with "this"... I know it calls an object from a class... In this case it would be calling the tile object when I click on the div element that i am creating.. but on console I get this error:

Uncaught TypeError: this.id is undefined
    setTile http://127.0.0.1:5501/js/game.js:54
    setGame http://127.0.0.1:5501/js/game.js:43
    onload http://127.0.0.1:5501/js/game.js:27
    EventHandlerNonNull* http://127.0.0.1:5501/js/game.js:26

With the first function setGame().. I create the divs that create the 3 x 3 board and give each div ids like 0,0 0,1 0,2 1,0 .. etc. And add an eventlistener so when we click in any of them.. it executes the following function, setTile()

When setTile() is called on the click, I was expecting to get the id from tiles (div element), calling tiles with this. to convert the string into an array and use them to tell the html where the current Player is on the board.


Solution

  • This can be solved in multiple ways. But first you need to know that arrow function expressions don't handle this the same as a regular function expression. So in your setTile function, this will either reference the first regular function expression that is a wrapping your code, or the window object.

    Besides that, using this in an event handler can be hard to understand as this could reference many things beside the element that the event listener has been added to.

    The easiest workaround is to pass the tile element to the setTile function as an argument and use the passed tile instead of this.

    const setGame = () => {
      board = [
        [' ', ' ', ' '],
        [' ', ' ', ' '],
        [' ', ' ', ' ']
      ];
    
      const boardElement = document.getElementById("board");
    
      for (let r = 0; r < 3; r++) {
        for (let c = 0; c < 3; c++) {
          let tile = document.createElement("div")
          tile.id = r.toString() + "-" + c.toString();
          tile.classList.add("col-4", "box", "d-flex", "justify-content-center", "align-items-center");
    
          tile.addEventListener("click", () => {
            setTile(tile);
          });
    
          boardElement.append(tile);
        }
      }
    }
    
    const setTile = (tile) => {
      if (gameOver) {
        return;
      }
    
      let coords = tile.id.split("-") //splits the id string "1-1" into an array["1", "1"]
      let r = parseInt(coords[0]);
      let c = parseInt(coords[1]);
    
      board[r][c] = currPlayer;
      tile.innerText = currPlayer;
    }