There are few things that need to be fixed.
- In your
handleClick
, you are using setSquares((squares) => squares.splice(i, 1, "X"))
. squares.splice
will mutate the state variable squares
which one should never do.
- Also
splice
doesn't return updated array but
An array containing the deleted elements.
This causes squares
from [null, null, null, null, null, null, null, null, null]
to [null]
causing you board to become blank for a moment. Then your setSquares((squares) => [...squares2])
kicks in from setTimeout
making squares
back to array making your square values visible again hence, the flash.
computersTurnHandler
might return undefined
in some cases where none of the return
statements trigger hence
Uncaught TypeError: squares2 is undefined handleClick Board.js:34
- You need to prevent user from clicking until your
setTimeout
is complete else multiple rapid clicks by user will cause inconsistent behaviour.
As for your query
is it acceptable at all to use Promises in that case
Usually situations like these can simply be solved by making use of temporary variable in most cases.
Now the Solution with promise and fixed handleClick
and computersTurnHandler
is as follows
// add this state variable
const [allowClick, setAllowClick] = useState(true);
const handleClick = (i) => {
if (!allowClick || calculateWinner(squares) || squares[i]) {
return;
}
// use this temporary variable to keep things in sync and get around the
// asyn nature of 'setSquare'
let squares2 = squares.slice();
// disable user move until setTimeout executes
setAllowClick(false);
new Promise((resolve) => {
squares2[i] = "X";
setSquares(squares2);
setXIsNext(!xIsNext);
// since we are using temporary variable 'squares2',
// we don't care much about value to be passed in resolve
// we could simply do 'resolve()'
resolve(squares2);
})
// if 'resolve()' is used then we use
// '.then(() => {....})'
.then((sqr) => {
if (!squares2.includes(null)) {
return squares2;
}
setTimeout(() => {
squares2 = computersTurnHandler(squares2);
setSquares(squares2);
// now user can make next move
setAllowClick(true)
}, 500);
})
.then(() => {
setXIsNext(!xIsNext);
});
if (!squares.includes(null)) {
setIsFinished(true);
console.log(isFinished);
}
};
const computersTurnHandler = (sq1) => {
const lines = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6]
];
const sq = [...sq1];
for (let i = 0; i < lines.length; i++) {
const [a, b, c] = lines[i];
if (sq[a] === "O" && sq[b] === "O" && sq[c] === null) {
sq[c] = "O";
return sq;
}
if (sq[a] === "O" && sq[c] === "O" && sq[b] && null) {
sq[b] = "O";
return sq;
}
if (sq[b] === "O" && sq[c] === "O" && sq[a] === null) {
sq[a] = "O";
return sq;
}
if (sq[a] === "X" && squares[b] === "X" && sq[c] === null) {
sq[c] = "O";
return sq;
}
if (sq[a] === "X" && sq[c] === "X" && sq[b] && null) {
sq[b] = "O";
return sq;
}
if (sq[b] === "X" && sq[c] === "X" && sq[a] === null) {
sq[a] = "O";
return sq;
}
}
let random;
while (!sq[random]) {
random = Math.floor(Math.random() * sq.length);
if (!sq[random]) {
sq[random] = "O";
return sq;
}
}
// if no other retuns are triggered then return sq
return sq;
};
Keeping fixes in computersTurnHandler
below is handleClick
without promise
const handleClick = (i) => {
if (isFinished || squares[i]) {
return;
}
let squares2 = squares.slice();
squares2[i] = "X";
if (calculateWinner(squares2) || !squares2.includes(null)) {
setSquares(squares2);
setIsFinished(true);
return;
}
squares2 = computersTurnHandler(squares2);
if (calculateWinner(squares2) || !squares2.includes(null)) {
setSquares(squares2);
setIsFinished(true);
return;
}
setSquares(squares2);
};
Note that in case of solution without promise turn will always be X
since move by computer will be synchronous in nature. Also computersTurnHandler
might need some more fixing since in some cases computer stops making move.