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?
Squareis a React component declared within another React component,Board, and so when you enqueue aturnstate update inBoardit re-renders and re-declares aSquarecomponent definition... all currentSquarecomponents 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, moveSquareout ofBoardand the issue likely goes away. Voting to close as "not reproducible or was caused by typo", effectively just a misunderstanding how React works.<Square turn={turn} setTurn={setTurn} />or<Square {...{turn, setTurn}} />Squareout ofBoardseems to work just fine in your sandbox... I see no issue/error with the passedsetTurnprop callback function. It's unclear what further issue you have still on your end.