Updating state of array didn’t work like expected!
I’m making a Tic-Tac-Toe Game with this detail:
I have a state called gameBoardState (an 3×3 array) that contains the position of ‘X’ & ‘O’ with initial value of all ‘null’.
**const initialGameBoard = [
[null, null, null],
[null, null, null],
[null, null, null],
];
**
export default function GameBoard({
playerTurnHandler,
onTurn,
turnLogger,
participant,
}) {
**const [gameBoardState, setGameBoardState] = useState(initialGameBoard);**
const [winner, setWinner] = useState('');
const [counter, setCounter] = useState(0);
.
.
.
}
When the game is over there’s a ‘rematch’ button that set the gameBoardState back to the initial state – which is all null.
Inside the onClick listener function, I copied the old value of the variable ‘initialGameBoard’ into a new const variable and then use this variable as the parameter of setGameBoardState(param). It didn’t work 🙁
I also tried setGameBoardState(initialGameBoard). It didn’t work too.
But when I initialized a new const variable inside the onClick function and directly use this variable inside setGameBoardState it did work!
Can someone explain to me why did it happen?
This is my complete code:
import { useState, useSyncExternalStore } from 'react';
import { WINNING_COMBINATIONS } from '../winning-combinations';
import GameOver from './GameOver';
const initialGameBoard = [
[null, null, null],
[null, null, null],
[null, null, null],
];
export default function GameBoard({
playerTurnHandler,
onTurn,
turnLogger,
participant,
}) {
const [gameBoardState, setGameBoardState] = useState(initialGameBoard);
const [winner, setWinner] = useState('');
const [counter, setCounter] = useState(0);
function checkWinner(actualGameboardState) {
const numberOfCombinations = WINNING_COMBINATIONS.length;
for (let index = 0; index < numberOfCombinations; index++) {
const firstBlock = WINNING_COMBINATIONS[index][0]; //example = {row: 0, column: 0}
const secondBlock = WINNING_COMBINATIONS[index][1]; //example = {row: 0, column: 1}
const thirdBlock = WINNING_COMBINATIONS[index][2]; //example = {row: 0, column: 2}
if (
actualGameboardState[firstBlock.row][firstBlock.column] === null ||
actualGameboardState[secondBlock.row][secondBlock.column] === null ||
actualGameboardState[thirdBlock.row][thirdBlock.column] === null
) {
continue; //if null, directly check the next combination
}
if (
actualGameboardState[firstBlock.row][firstBlock.column] === 'X' &&
actualGameboardState[secondBlock.row][secondBlock.column] === 'X' &&
actualGameboardState[thirdBlock.row][thirdBlock.column] === 'X'
) {
//do something
setWinner(participant[0].toUpperCase());
}
if (
actualGameboardState[firstBlock.row][firstBlock.column] === 'O' &&
actualGameboardState[secondBlock.row][secondBlock.column] === 'O' &&
actualGameboardState[thirdBlock.row][thirdBlock.column] === 'O'
) {
//do something
setWinner(participant[1].toUpperCase());
}
}
}
function selectSquareHandler(row, col) {
const newGbState = [...gameBoardState];
newGbState[row][col] = onTurn;
setGameBoardState(newGbState);
playerTurnHandler();
turnLogger(row, col, onTurn);
checkWinner(gameBoardState);
setCounter((prev) => prev + 1);
}
function rematch() {
const initial = [...initialGameBoard];
const initial2 = [
[null, null, null],
[null, null, null],
[null, null, null],
];
//setGameBoardState(initial); doesn't work! page isn't re-rendered
//setGameBoardState(initialGameBoard) also doesn't work
setGameBoardState(initial2);//works perfectly!
setWinner('');
setCounter(0);
console.log(gameBoardState, winner, counter);
}
return (
<>
{(winner || counter === 9) && (
<GameOver winner={winner} rematch={rematch} />
)}
<ol id="game-board">
{gameBoardState.map((row, rowIndex) => (
<li key={rowIndex}>
<ol>
{row.map((playerSymbol, colIndex) => (
<li key={colIndex}>
<button
onClick={() => selectSquareHandler(rowIndex, colIndex)}
disabled={playerSymbol !== null ? true : false}
>
{playerSymbol}
</button>
</li>
))}
</ol>
</li>
))}
</ol>
</>
);
}
2
Answers
The problem is this line
const newGbState = [...gameBoardState];
Spead operator can only shallow copy your object/array means that in cases of nested objects/arrays their
ref
gonna stay the sameOne way to fix this is to change the line of code above to something likes this
The arrays within your state have their own references. When you spread them, the references do not change. You’ll need to create new references for your inner array as well. If you
console.log(initialGameBoard)
you’ll notice the values change as you modify the board. An east way to do this is withstructuredClone
Here’s a reproduceable example:
@The Teabag Coder’s solution will also solve your issue. I’m posting mine just to point out the root cause of the problem.