skip to Main Content

Learning react here and doing the tic-tac-toe intro here: https://react.dev/learn/tutorial-tic-tac-toe.

I’m trying to understand why my code isn’t working. I’m on the https://react.dev/learn/tutorial-tic-tac-toe#completing-the-game portion, where you add components like:

<Square value={squares[8]} onSquareClick={() => handleClick(8)} />

I’m trying to take a slightly different approach and create the squares programmatically, like so:


export default function Board() {
  const [squares, setSquares] = useState(Array(9).fill(null));

  function handleClick(i) {
    const nextSquares = squares.slice();
    nextSquares[i] = "X";
    setSquares(nextSquares);
  }

  var boardRows = [];
  var counter = 0;

  for (var i = 0; i < 3; i++) {
    var boardSquares = [];
    for (var j = 0; j < 3; j++) {
      boardSquares.push(<Square value={squares[counter]} onSquareClick={() => handleClick(i)} />);
      counter ++;
    }

    boardRows.push(<div className="board-row"> {boardSquares} </div>);
  }

  return boardRows;
}

The problem is, the handleClick(i) is using 9, since that’s the final value of my counter. Is there a way to have the function "save" the value that will be passed in on the click? Or am I going about this wrong?

2

Answers


  1. The issue you’re facing is due to the fact that the handleClick function you’re passing to the onSquareClick prop inside the loop is always referencing the final value of i (which is 2 in this case). This happens because JavaScript closures capture the current value of variables.

    To fix this issue, you can modify your code by creating a new function within the loop that captures the value of i for each iteration. This way, the correct value of i will be used when calling the handleClick function. Here’s how you can do it:

    export default function Board() {
      const [squares, setSquares] = useState(Array(9).fill(null));
    
      function handleClick(i) {
        const nextSquares = squares.slice();
        nextSquares[i] = "X";
        setSquares(nextSquares);
      }
    
      var boardRows = [];
      var counter = 0;
    
      for (var i = 0; i < 3; i++) {
        var boardSquares = [];
        for (var j = 0; j < 3; j++) {
          const index = counter; // Capture the current value of counter
          boardSquares.push(<Square value={squares[index]} onSquareClick={() => handleClick(index)} />);
          counter++;
        }
    
        boardRows.push(<div className="board-row"> {boardSquares} </div>);
      }
    
      return boardRows;
    }
    

    By creating a const index = counter; within the inner loop and using index inside the onSquareClick function, you ensure that the correct value of index is captured and used for each square’s click event. This should solve the issue you were facing with the incorrect value of i in the handleClick function.

    Login or Signup to reply.
  2. There are many problems

    1. The i you are passing in is only 0~2 due to its using the counter from the outer loop. Although it’s not what you are asking, the next one will answer your answer
    2. Because as Andy suggested in the comment, values that are declared with var are available throughout the function (in this case Board), so i will always be the last value at the end of the loop. You could fix that by declaring with let but that doesn’t completely solve your problem, which leads to the next problem…
    3. The value you are passing in not only is available throughout the function Board, but is not accurate. The easiest way to properly pass the value would be to define a value right before you pass it. In 95% of the cases, const is your best bet. Below is the proper fix —
      var boardRows = []
      var counter = 0
    
      for (let i = 0; i < 3; i++) {
        var boardSquares = []
        for (var j = 0; j < 3; j++) {
          const key = (i *3) + j /* declare const value here */
          boardSquares.push(
            <Square
              key={key}
              value={squares[counter]}
              onSquareClick={() => handleClick(key)}
            />
          )
          counter++
        }
    
        boardRows.push(<div className="board-row"> {boardSquares} </div>)
      }
    

    The code here not only answers your question, and also fixes the bug where the square you are clicking isn’t aligned with what it is intended.

    const value is block scope, and isn’t mutatable, making it extra safe as a disposable value.

    Hope this helps.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search