0

I'm new to React and am playing around with their Tic-Tac-Toe tutorial. I wanted to try generating the Squares using a for-loop but am running into an issue that I'm not quite understanding. That part of my code (placed at the bottom of the Board function) looks like this:

   const retVal = Array(3).fill(null);
    for (var i = 0; i < 10; i += 3) {
        if (i < 3) {
            retVal[0] = (
                <>
                    <Square value={squares[i]} onSquareClick={() => handleClick(i)} />
                    <Square value={squares[i + 1]} onSquareClick={() => handleClick(i + 1)} />
                    <Square value={squares[i + 2]} onSquareClick={() => handleClick(i + 2)} />
                </>
            );
        }
        else if (i >= 3 && i < 6) {
            retVal[1] = (
                <>
                    <Square value={squares[i]} onSquareClick={() => handleClick(i)} />
                    <Square value={squares[i + 1]} onSquareClick={() => handleClick(i + 1)} />
                    <Square value={squares[i + 2]} onSquareClick={() => handleClick(i + 2)} />
                </>
            );
        }
        else {
            retVal[2] = (
                <>
                    <Square value={squares[i]} onSquareClick={() => handleClick(i)} />
                    <Square value={squares[i + 1]} onSquareClick={() => handleClick(i + 1)} />
                    <Square value={squares[i + 2]} onSquareClick={() => handleClick(i + 2)} />
                </>
            );
        }
    }

    return (
        <>
            <div className="status">{status}</div>
            <div className="board-row">
                {retVal[0]}
            </div>
            <div className="board-row">
                {retVal[1]}
            </div>
            <div className="board-row">
                {retVal[2]}
            </div>
        </>
    );

I realize this may not be the most elegant solution for generating the squares (and the tutorial mentions using two loops for this without showing an example) but that's a secondary question. The issue is that when handleClick(i) gets called the value of i received by this event handler is not what I was expecting. For example, when clicking the first square the value received for i is 12 and not 0. Subsequently, all the other squares also have unexpected i values. I've verified that i is 0 when retVal[0] is created so this appears to have something to do with how React renders the output since i is being set to a value outside the range of the for-loop. Trying to understand why (or figure out if I'm missing an obvious bug.)

Mike Lowery
  • 2,630
  • 4
  • 34
  • 44
  • Related question: https://stackoverflow.com/questions/45572856/implementation-of-two-for-loops-in-react-js-to-create-a-3x3-square-marix-box-for – Mike Lowery May 01 '23 at 16:50

3 Answers3

1

The solution for this turned out to be quite simple. Coming from a C# background I instinctively used the var keyword inside the for loop. In JS, var is function-scoped so the value assigned to i persisted outside of the loop which is why I believe I was seeing incorrect values from the return statement. Changing var to let solved the problem:

for (let i = 0; i < 9; i += 3) { ... }

(I also changed i < 10 to i < 9 since that was causing a different error.)

Mike Lowery
  • 2,630
  • 4
  • 34
  • 44
0

I don't think this loop will work like you intend.

For instance this conditional code:

      if (i < 3) {
            retVal[0] = (
                <>
                    <Square value={squares[i]} onSquareClick={() => handleClick(i)} />
                    <Square value={squares[i + 1]} onSquareClick={() => handleClick(i + 1)} />
                    <Square value={squares[i + 2]} onSquareClick={() => handleClick(i + 2)} />
                </>
            );
        }

Will run three times, when i=0, i=1, and i=2. So your top row (retVal[0]) will end up with the third (i=2) version because it will override the Squares you created the first two times.

Jay
  • 42
  • 4
  • When I replace `i` with constants it works fine. And it's not running this section three times because the loop adds 3 to `i` on each pass. It runs once when `i` equals 0. – Mike Lowery Apr 28 '23 at 03:34
  • oh sorry, i missed that. `i` is coming back as 12 because it points to `i` by reference so `i` is 12 for the entire function whether that if condition runs or not. if you don't want to use constants try copying i to another variable before passing it to `handleClick()` – Jay Apr 28 '23 at 19:47
  • 1
    Adding `const j = i` immediately after the `for` loop and using `j` inside the JSX worked, but not sure I'm fully understanding why. Is it because the value of `i` is not evaluated until the `return` statement (pass by reference) but the value of `j` is only evaluated inside the loop? Is there documentation somewhere that explains this behavior better? – Mike Lowery Apr 30 '23 at 19:27
0

I think you need to use useEffect react hook to create the retVal variable.

And, I would like to know where did you declare the handleClick() function if you don't mind.

Alan Ma
  • 26
  • 1
  • 4
  • The `handleClick()` event is shown in my link to the Tic-Tac-Toe example above. I'm not sure that `useEffect` is necessary since it’s completely fine to change variables and objects that you’ve _just_ created while rendering. Here's another [example](https://codesandbox.io/s/nyist5?file=/App.js&utm_medium=sandpack) where they're doing essentially the same thing as my function. – Mike Lowery May 01 '23 at 18:39
  • Just I am afraid the looping statement has not been rendered even till the `return` function has run at the first loading time. – Alan Ma May 02 '23 at 20:17