Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Elements in a for loop lose hooks in React

I’m learning React by following the tic-tac-toe exercise shown in this page. In the Wrapping up chapter, one of the extra assignments is:

Rewrite Board to use two loops to make the squares instead of hardcoding them.

So I went from this:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

function Board({ xIsNext, squares, onPlay }) {
  function handleClick(i) {
    if (calculateWinner(squares) || squares[i]) {
      return;
    }
    const nextSquares = squares.slice();
    if (xIsNext) {
      nextSquares[i] = 'X';
    } else {
      nextSquares[i] = 'O';
    }
    onPlay(nextSquares);
  }

  const winner = calculateWinner(squares);
  let status;
  if (winner) {
    status = 'Winner: ' + winner;
  } else {
    status = 'Next player: ' + (xIsNext ? 'X' : 'O');
  }

  return (
    <>
      <div className="status">{status}</div>
      <div className="board-row">
        <Square value={squares[0]} onSquareClick={() => handleClick(0)} />
        <Square value={squares[1]} onSquareClick={() => handleClick(1)} />
        <Square value={squares[2]} onSquareClick={() => handleClick(2)} />
      </div>
      <div className="board-row">
        <Square value={squares[3]} onSquareClick={() => handleClick(3)} />
        <Square value={squares[4]} onSquareClick={() => handleClick(4)} />
        <Square value={squares[5]} onSquareClick={() => handleClick(5)} />
      </div>
      <div className="board-row">
        <Square value={squares[6]} onSquareClick={() => handleClick(6)} />
        <Square value={squares[7]} onSquareClick={() => handleClick(7)} />
        <Square value={squares[8]} onSquareClick={() => handleClick(8)} />
      </div>
    </>
  );
}

To this, see the changes comment for the difference:

function Board({ xIsNext, squares, onPlay }) {

  function handleClick(i) {
    if (squares[i] || calculateWinner(squares)) {
      return;
    }
    const nextSquares = squares.slice();
    if (xIsNext) {
      nextSquares[i] = "X";
    }
    else {
      nextSquares[i] = "O";
    }
    onPlay(nextSquares);
  }

  const winner = calculateWinner(squares);
  let status;
  if (winner) {
    status = "Winner: " + winner;
  }
  else {
    status = "Next player: " + (xIsNext ? "X" : "O");
  }

  /* CHANGES START HERE */
  let content = [];
  let n=0;
  for (let i=0; i<3; i++) {
    let row = [];
    for (let j=0; j<3; j++) {
      row.push(<Square key={"square-" + n} value={squares[n]} onSquareClick={() => handleClick(n)} />);
      n++;
    }
    content.push(<div key={"row-" + i} className="board-row">{row}</div>);
  }

  return (
    <>
      <div className="status">{status}</div>
      {content}
    </>
  );
}

The Board is correctly drawn and also the keys for the elements in the array are generated correctly as requested by the compiler:

running buggy app

However, if I click on a square, the cell doesn’t get the X/O sign as it should, also the game tracker just logs the first move and ignores any subsequent click on other squares.

clicked on top-left square

I probably would have used a .map() as the main docs suggest, but since the assignment specifically mentions two loops, I don’t know what I’m doing wrong.

I also took a look at this answer, but I have extra data I wouldn’t know where to place, such as the className and keys.

Thanks in advance for your help!

>Solution :

I believe that the problem is not related to react, it’s just that you declared your n variable before the loops, so it has always the latest value (which is 9)

Your code simulation (click any number):

let n = 0
for (let i = 0; i < 3; i += 1) {
  for (let j = 0; j < 3; j += 1) {
    const el = document.createElement('div')
    el.textContent = n
    el.addEventListener('click', () => handleClick(n))
    document.body.appendChild(el)
    n += 1
  }
}

function handleClick(n) {
  console.log(n)
}

You should calculate n based on i and j instead

Fixex:

for (let i = 0; i < 3; i += 1) {
  for (let j = 0; j < 3; j += 1) {
    const n = i * 3 + j
    const el = document.createElement('div')
    el.textContent = n
    el.addEventListener('click', () => handleClick(n))
    document.body.appendChild(el)
  }
}

function handleClick(n) {
  console.log(n)
}
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading