skip to Main Content

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


  1. 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 same

    One way to fix this is to change the line of code above to something likes this

    const newGbState = [];
    for(let i=0; i<gameBoardState.length; i++) {
      newGbState[i] = [...gameBoardState[i]];
    }
    
    Login or Signup to reply.
  2. 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 with structuredClone

    const newGbState = [...gameBoardState];
    // This is modifying your state!
    // Do `const newGbState = structuredClone(gameBoardState)` instead
    newGbState[row][col] = onTurn;
    

    Here’s a reproduceable example:

    const initialGameBoard = [
      [null, null, null],
      [null, null, null],
      [null, null, null],
    ];
    
    initialGameBoard[0][1] = 1;
    const x = [...initialGameBoard];
    
    console.log(x[0][1]) // 1
    
    x[0][0] = 0;
    console.log(initialGameBoard[0][0]); // 0
    

    @The Teabag Coder’s solution will also solve your issue. I’m posting mine just to point out the root cause of the problem.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search