Wait for `insertAdjacentElement` to update DOM

27 Views Asked by At

I am practicing html/vanilla JS by building a tic tac toe app. I add an event listener to the board square <div>s, and create elements with X and O and insert them into the DOM, then call a function to check if there is a winner. The checkIfPlayerWins() function does some checking and then calls and alert if the players wins or there is a draw.

The problem I am running into is that if a player wins, the alert is being called before the X or O is inserted into the DOM which just looks funny. I would like to not use a setTimeout as that just seems a bit hacky. I tried async/await and also creating a new Promise from it but can't seem to get it to work.

Okay here is some code and relevant links: insertAdjacentElement MDN docs My github/ code repo

  setEventListeners() {
    const board = document.querySelector('.board')
    const squares = board.children

    for (let i = 0; i < squares.length; i++) {
      squares[i].addEventListener('click', (event) => {
        const squareEl = event.target
        if (!squareEl.firstChild) {
          this.makeMove(squareEl)
        } else {
          alert('That square is already taken!')
        }
      })
    }
  }

  makeMove(eventTarget) {
    let playerMarker = ''
    if (this.playerOneTurn) {
      playerMarker = 'X'
    } else {
      playerMarker = 'O'
    }

    eventTarget.insertAdjacentElement('beforeend', createElementFromHTML(`<div>${playerMarker}</div>`))

    this.updateGameState(eventTarget.className, playerMarker)

    this.playerOneTurn = !this.playerOneTurn
    this.updatePlayerTurnText()
  }

export function createElementFromHTML(htmlString) {
  const template = document.createElement('template')
  template.innerHTML = htmlString.trim()
  return template.content.firstChild
}

  updateGameState(squareName, playerMarker) {
    this.gameState[squareName] = playerMarker
    this.checkIfPlayerWins()
  }

  checkIfPlayerWins() {
    // TODO make this smarter
    const winningIndexes = [
      [0, 1, 2],
      [3, 4, 5],
      [6, 7, 8],
      [0, 3, 6],
      [1, 4, 7],
      [2, 5, 8],
      [0, 4, 8],
      [2, 4, 6],
    ]

    let winner = null

    winningIndexes.forEach((combos) => {
      if (combos.every((combo) => this.gameState[`square-${combo + 1}`] === 'X')) {
        winner = this.playerOne
      } else if (combos.every((combo) => this.gameState[`square-${combo + 1}`] === 'O')) {
        winner = this.playerTwo
      }
    })

    if (Object.values(this.gameState).every((square) => square != '')) {
      alert('Game is a draw!')
    } else if (winner) {
      alert(`${winner} wins!!`)
    }
  }

These are all the relevant functions, and if you see in the makeMove() function, I call eventTarget.insertAdjacentElement, but then I call updatePlayerTurn() which calls checkIfPlayerWins() which calls the alert(). This alert() in the checkIfPlayerWins() function is executing before the insertAdjacentElement element gets inserted into the DOM. Can anyone help me figure out how I can wait for the DOM changes to happen before the alert() gets called? Thanks!

0

There are 0 best solutions below