-1

I was trying to learn React using this tutorial: https://react.dev/learn/tutorial-tic-tac-toe.

The code I am sharing below just builds 9 squares, and creates two state variables: turn and value. The only thing I am trying to achieve is that on click, the value becomes X if turn === 1 and O if turn == 2.

Implementation(Look at handleClick fn): Two ways of updating the turn variable (at both the If and Else blocks). Either I update it using assignment (eg turn = 2) or I use setTurn(2). If I use assignment method, value is set accordingly to either "x" or "O" (as per the setValue calls). But if I use setTurn method to change the turn (after having called setValue) , value never changes (and the squares are always blank) I

Please go to https://codesandbox.io/p/sandbox/878wt9?file=%2Fsrc%2FApp.js and paste this code in App.js to check for yourself.

What actually is happening? How does react decide which function to call when a state change happens? A bit of theory (in terms of scope, variables, closures, etc) would be helpful.

import { useState } from "react";

export default function Board() {
  let [turn, setTurn] = useState(1);
  // let turn = 1;

  const Square = () => {
    const [value, setValue] = useState(null);

    const handleClick = () => {
    
      if (turn === 1) {
        setValue("X");

        turn = 2; // This code works.
        // setTurn(2) // This doesnt work.
      } else {
        setValue("O");

        turn = 1; // This does work.
        // setTurn(1) // This doesnt work.
      }
    };

    console.log(`value ${value}`);

    return (
      <button className="square" onClick={handleClick}>
        {value}
      </button>
    );
  };

  return (
    <>
      <div className="board-row">
        <Square />
        <Square />
        <Square />
      </div>
      <div className="board-row">
        <Square />
        <Square />
        <Square />
      </div>
      <div className="board-row">
        <Square />
        <Square />
        <Square />
      </div>
    </>
  );
}

As others have pointed out, I should really move Square out of Board's scope. I have tried that in the following code. This runs fine. (A previous version of this question said "I was getting TypeError: setTurn is not a function." This probably had to do with bad local environment on my laptop)

import { useState } from "react";

export default function Board() {
  let [turn, setTurn] = useState(1);
  console.log("Called Board");

  // let turn = 1;


  return (
    <>
      <div className="board-row">
        <Square turn={turn} setTurn={setTurn} />
        <Square turn={turn} setTurn={setTurn} />
        <Square turn={turn} setTurn={setTurn} />


      </div>
      <div className="board-row">

        <Square turn={turn} setTurn={setTurn} />
        <Square turn={turn} setTurn={setTurn} />
        <Square turn={turn} setTurn={setTurn} />

      </div>
      <div className="board-row">

        <Square turn={turn} setTurn={setTurn} />
        <Square turn={turn} setTurn={setTurn} />
        <Square turn={turn} setTurn={setTurn} />
      </div>
    </>
  );
}

  const Square = ({turn, setTurn}) => {
    console.log("Called Square");
    const [value, setValue] = useState(null);

    const handleClick = () => {
      if (turn === 1) {
        console.log(`1.setValue if: turn:${turn} value:${value}`);
        setValue("X");

        console.log(`2.setTurn if: turn:${turn} value:${value}`);
        // turn = 2; // This code works.
        setTurn(2);
      } else {
        console.log(`3.setValue else: turn:${turn} value:${value}`);
        setValue("O");

        console.log(`4.setTurn else: turn:${turn} value:${value}`);
        // turn = 1; // This does work.
        setTurn(1);
      }
    };

    return (
      <button className="square" onClick={handleClick}>
        {value}
      </button>
    );
  };

Another thing I tried is trying to check how the re-rendering calls are enqueued by the following code

import { useState } from "react";

export default function Board() {
  console.log(`1.bf useState Called Board`);

  let [turn, setTurn] = useState(1);
  console.log(`2.af useState Called Board, turn is ${turn}`);

  // let turn = 1;

  const Square = () => {
    console.log(`3.bf useState: Called Square, turn:${turn}`);

    const [value, setValue] = useState(null);
    console.log(`4.af useState: Called Square, turn:${turn} value:${value}`);

    const handleClick = () => {
      if (turn === 1) {
        console.log(`5..setTurn if: turn:${turn} value:${value}`);
        setTurn(2);

        console.log(`6..setValue if: turn:${turn} value:${value}`);
        setValue("X");
        console.log(`7..setValue if: turn:${turn} value:${value}`);

        // turn = 2; // This code works.
      } else {
        console.log(`8..setTurn else: turn:${turn} value:${value}`);
        setTurn(1);

        console.log(`9.setValue else: turn:${turn} value:${value}`);
        setValue("O");
        console.log(`10..setValue if: turn:${turn} value:${value}`);

        // turn = 1; // This does work.
      }
    };

    return (
      <button className="square" onClick={handleClick}>
        {value}
      </button>
    );
  };

  console.log("End of BOARD");
  return (
    <>
      <div className="board-row">
        <Square />
        <Square />
        <Square />
      </div>
      <div className="board-row">
        <Square />
        <Square />
        <Square />
      </div>
      <div className="board-row">
        <Square />
        <Square />
        <Square />
      </div>
    </>
  );
}

The output this one gives, in one click through the square is this

5..setTurn if: turn:1 value:null
6..setValue if: turn:1 value:null
7..setValue if: turn:1 value:null
1.bf useState Called Board
2.af useState Called Board, turn is 2
End of BOARD
1.bf useState Called Board
2.af useState Called Board, turn is 2
End of BOARD
3.bf useState: Called Square, turn:2
4.af useState: Called Square, turn:2 value:null
3.bf useState: Called Square, turn:2
4.af useState: Called Square, turn:2 value:null
3.bf useState: Called Square, turn:2
4.af useState: Called Square, turn:2 value:null
3.bf useState: Called Square, turn:2

Looking at these logs, I do see that Board is called twice, but why is Square called three times and that too after the calls to Board have ended? Then only line 3. is logge (and Square seems to exit or not sure what happend) . As I mentioned in the comments, I couldn't make much of it. Any specific parts of the documentation, that could explain what happened here?

17
  • 1
    @KanchanKumar "A bit of theory (in terms of scope, variables, closures , etc) would be helpful." - you're misusing react components by nesting Square inside Board, you shouldn't you do that, the problems you describe are caused by that . The tutorial you mention doesn't show this. If the reason you did that is because you wanted to access "turn" inside Square, you did it the wrong way. In react, you pass values from a parent to children through props or context, check the relevant parts of the docs Commented Nov 24 at 16:47
  • 1
    @KanchanKumar It shouldn't be nested inside Board at all. It's unclear if you fix this Commented Nov 24 at 17:08
  • 1
    Square is a React component declared within another React component, Board, and so when you enqueue a turn state update in Board it re-renders and re-declares a Square component definition... all current Square components unmount and new ones replace them, all with initial React state. This is why you never see them update to "X" or "O". As others have pointed out, move Square out of Board and the issue likely goes away. Voting to close as "not reproducible or was caused by typo", effectively just a misunderstanding how React works. Commented Nov 24 at 17:36
  • 1
    @KanchanKumar You have a basic problem with JSX syntax, it's either <Square turn={turn} setTurn={setTurn} /> or <Square {...{turn, setTurn}} /> Commented Nov 24 at 18:06
  • 2
    The updated code you shared where you pulled Square out of Board seems to work just fine in your sandbox... I see no issue/error with the passed setTurn prop callback function. It's unclear what further issue you have still on your end. Commented Nov 24 at 20:55

1 Answer 1

2

I've never seen a react component function defined within a closure. State inside each component definition is considered ephemeral--that's why you use hooks. By defining your component inside another component, that component definition itself is ephemeral.

Start by breaking out the component definitions outside of a closure, just as it is in the example you are copying from.

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.