I recently started learning react and decided to create a Tic Tac Toe game, and I have encountered an issue where even after winning the game an extra move is required to log out winner.
Here is the relevant code:
//TicTacToe.jsx
import { useState, useEffect } from "react";
import "../styles/board.css";
function TicTacToe() {
const [squares, setSquares] = useState(Array(9).fill(null));
const [isClicked, setIsClicked] = useState(Array(9).fill(false));
const [turnX, setTurnX] = useState(true);
const [winner, setWinner] = useState(null);
useEffect(() => {
if (winner !== null) {
if (winner) {
console.log("X is the winner");
} else {
console.log("O is the winner");
}
}
}, [winner]);
const checkWinner = () => {
if (winner !== null) return;
const winningCombinations = [
[0, 1, 2], // horizontal
[3, 4, 5],
[6, 7, 8],
[0, 3, 6], // vertical
[1, 4, 7],
[2, 5, 8],
[0, 4, 8], // diagonal
[2, 4, 6],
];
winningCombinations.forEach((combination) => {
const [a, b, c] = combination;
match(a, b, c);
});
};
const match = (a, b, c) => {
if (
squares[a] === squares[b] &&
squares[b] === squares[c] &&
squares[a] !== null
) {
if (squares[a] === "X") {
setWinner(true); // X winner
} else {
setWinner(false); // O winner
}
}
};
const handleClick = (index) => {
// don't make multiple click possible
if (isClicked[index]) return;
// do immutable updates
const newSquares = [...squares];
const newClicked = [...isClicked];
// print the click
newSquares[index] = printXO();
newClicked[index] = true;
// change state
setSquares(newSquares);
checkWinner(); // Check for a winner after updating the squares state
setTurnX(!turnX);
setIsClicked(newClicked);
};
const printXO = () => {
return turnX ? "X" : "O";
};
return (
<div>
<div className="board">
{squares.map((square, index) => (
// map the squares with div '_00' etc.
<div
className="square"
id={`_${Math.floor(index / 3)}${index % 3}`}
key={index}
onClick={() => handleClick(index)}
>
{square}
</div>
))}
</div>
</div>
);
}
export default TicTacToe;
/* board.css */
.board {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-gap: 10px;
width: 300px;
height: 300px;
margin: 0 auto;
background-color: #ddd;
}
.square {
display: flex;
align-items: center;
justify-content: center;
font-size: 48px;
background-color: #fff;
cursor: pointer;
}
.square:hover {
background-color: #f2f2f2;
}
The issue I’m facing is that the winner doesn’t print when it is supposed to. After a winning move is made, the winner is only detected on the next move. It seems like there’s a delay in updating the winner state.
Can someone please help me in identifying the problem?
Thanks in advance for your help!
PS: I know the game is incomplete, I’ll complete it as soon as I fix this bug.
2
Answers
To verify the winner once the winner state is updated, utilize the useEffect hook. Modify your code as shown below:
By adding [squares] as a dependency in the useEffect hook, it will be triggered whenever the squares state changes. Consequently, the checkWinner() function will be executed after the squares state is modified, guaranteeing accurate detection of the winner.
Furthermore, you can simplify your match function by eliminating the unnecessary squares[a] !== null check since it is already handled within the checkWinner function. Here’s the updated match function:
With these modifications, the winner should be accurately determined without requiring an extra move.
The problem is that you are calling the checkWinner function before updating the squares state with the new move. This means that the
checkWinner
function is always one move behind the actual state of the game. To fix this, you need to call the checkWinner function after thesetSquares
state update has been completed. And you can improve performance usinguseCallback
hook.For example:
This is Codesandbox.