skip to Main Content

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:

code sandbox

//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


  1. To verify the winner once the winner state is updated, utilize the useEffect hook. Modify your code as shown below:

    useEffect(() => {
      checkWinner(); // Check for a winner after updating the squares state
    }, [squares]); // Add 'squares' as a dependency to the useEffect hook
    

    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:

    const match = (a, b, c) => {
      if (squares[a] === squares[b] && squares[b] === squares[c]) {
        if (squares[a] === "X") {
          setWinner(true); // X winner
        } else {
          setWinner(false); // O winner
        }
      }
    };
    
    

    With these modifications, the winner should be accurately determined without requiring an extra move.

    Login or Signup to reply.
  2. 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 the setSquares state update has been completed. And you can improve performance using useCallback hook.

    For example:

    function TicTacToe() {
      /* ... */
    
      const match = useCallback(
        (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
            }
          }
        },
        [squares]
      );
    
      const checkWinner = useCallback(() => {
        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);
        });
      }, [winner, match]);
    
      useEffect(() => {
        checkWinner();
      }, [squares, checkWinner]);
    
      useEffect(() => {
        if (winner !== null) {
          if (winner) {
            console.log("X is the winner");
          } else {
            console.log("O is the winner");
          }
        }
      }, [winner]);
    
      /* ... */
    
      return (...);
    }
    

    This is Codesandbox.

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