skip to Main Content

I’m building a tic-tac-toe game and able to have two players play against each other. Now I want to simulate randomness on Play O. Part of the randomness, is getting one of the 9 children components, "PositionedComponent", to simulate the computer doing the click and have it change the display.

I thought using useRef() and .click() would allow for this simulation. I created an array of useRef() and in each PositionedComponent, I give them each a useRef(). Then inside of useEffect() I thought of creating a random number and then call the click function in autoClick[randomValue].current.click()

I always get an undefined value for autoClick but I’m not sure why when I initialized it at the top.

I’ve also tried declaring the array as autoClick = useState(Array(9).fill(useRef())) as well as doing autoClick = [useRef()…,useRef()] and still the same undefined value issue.

Another method I tried using was document.getElementById("") but still the same undefined value issue. I’m not sure what is the issue since I’m declaring the autoClick at the top but still gettin undefined value. Thank you for any advice.

function TicTacToeGrid()
{
  // Your tic-tac-toe logic goes here
  const [playersMove, setPlayersMove] = useState(true);
  const [symbol, setSymbol] = useState('');
  const [currPosition, setCurrPosition] = useState(23)
  const [board, setBoard] = useState(Array(9).fill('-'));
  const [game, setGame] = useState(false)
  const  [tie, setTie] = useState(false)
  const autoClick = []

  for(let i = 0; i < 9; i++)
  {
    autoClick[i] = useRef();
  }
 

  
  const handlePlayersTurn = async (position) =>
  {
    console.log("auto click is", autoClick)
    if(playersMove)
    {
      setSymbol('X')
    }
    else
    {
      setSymbol('O')
    }
    setPlayersMove(!playersMove)    
    setCurrPosition(position)
  };
  
  const positon1 =  <PositionedComponent x="0px" y = "0px" ref={autoClick[0]} playersMove={playersMove} handlePlayersTurn={() => handlePlayersTurn(1)}/>
  const positon2 =  <PositionedComponent x="210px" y = "0px" ref={autoClick[1]} playersMove={playersMove} handlePlayersTurn={() =>handlePlayersTurn(2)}/>
  const positon6 =  <PositionedComponent x="420px" y = "0px"  ref={autoClick[2]}  playersMove={playersMove} handlePlayersTurn={() =>handlePlayersTurn(3)}/>
  const positon3 =  <PositionedComponent x="0px" y = "210px"  ref={autoClick[3]} playersMove={playersMove} handlePlayersTurn={() =>handlePlayersTurn(4)}/>
  const positon4 =  <PositionedComponent x="210px" y = "210px" ref={autoClick[4]}  playersMove={playersMove} handlePlayersTurn={() =>handlePlayersTurn(5)}/>
  const positon7 =  <PositionedComponent x="420px" y = "210px" ref={autoClick[5]} playersMove={playersMove} handlePlayersTurn={() =>handlePlayersTurn(6)}/>
  const positon5 =  <PositionedComponent x="0px" y = "420px"  ref={autoClick[6]}  playersMove={playersMove} handlePlayersTurn={() =>handlePlayersTurn(7)}/>
  const positon9 =  <PositionedComponent x="210px" y = "420px" ref={autoClick[7] playersMove={playersMove} handlePlayersTurn={() =>handlePlayersTurn(8)}/>
  const positon8 =  <PositionedComponent x="420px" y = "420px" ref={autoClick[8]} playersMove={playersMove} handlePlayersTurn={() =>handlePlayersTurn(9)}/>
  
  useEffect(()=>
  {
    if(playersMove)
    {
      console.log("X turn")
    }
    else
    {
      console.log("O turn")
      const randomValue = Math.floor(Math.random() * 10);
      autoClick[randomValue].current.click()
    }
    const newBoard = board
    newBoard[currPosition-1] = symbol
    const finalBoard = newBoard
    setBoard(finalBoard);
    setGame(isWinner(finalBoard, symbol));
    setTie(catScratch(board))
  })

3

Answers


  1. The autoClick was an initialised empty array in the component body, not triggering re-renders. This leads to undefined refs in the useEffect.

    try:

    const autoClick = Array(9).fill(null).map(() => useRef());
    
    Login or Signup to reply.
  2. The issue you are facing with autoClick being undefined is due to the fact that you are trying to access autoClick in the useEffect function, and at that point, autoClick is not defined within the scope of the useEffect. To fix this issue, you should declare autoClick using the useState hook, and you can also use the useRef hook to create the array of refs. Here’s an updated version of your code:

    import React, { useState, useRef, useEffect } from 'react';
    
    function TicTacToeGrid() {
      const [playersMove, setPlayersMove] = useState(true);
      const [symbol, setSymbol] = useState('');
      const [currPosition, setCurrPosition] = useState(0);
      const [board, setBoard] = useState(Array(9).fill('-'));
      const [game, setGame] = useState(false);
      const [tie, setTie] = useState(false);
    
      // Declare autoClick as a state variable and initialize it with an array of refs
      const [autoClick, setAutoClick] = useState(Array(9).fill(useRef()));
    
      const handlePlayersTurn = (position) => {
        if (playersMove) {
          setSymbol('X');
        } else {
          setSymbol('O');
        }
        setPlayersMove(!playersMove);
        setCurrPosition(position);
      };
    
      useEffect(() => {
        if (!playersMove) {
          console.log("O turn");
          const randomValue = Math.floor(Math.random() * 9);
          autoClick[randomValue].current.click();
        }
    
        const newBoard = [...board]; // Create a new array to avoid mutating the state directly
        newBoard[currPosition - 1] = symbol;
        setBoard(newBoard);
        setGame(isWinner(newBoard, symbol));
        setTie(catScratch(newBoard));
      }, [playersMove, symbol, currPosition, board, autoClick]);
    
      // Add the rest of your code for rendering components and game logic here
    
      return (
        // Your JSX code for rendering the game board and components
      );
    }
    
    Login or Signup to reply.
  3. In React everything is controlled by state / props. Instead of simulating a click, just change the state directly by calling handlePlayersTurn(position).

    In addition, you have too much states. If you need to derive a value (symbol from playerMove for example), you don’t need a different state, just compute it on the fly.

    const { useState, useEffect } = React
    
    const initGame = () => Array.from({ length: 9 }, (_, i) => ({
      position: i,
    }))
    
    function TicTacToeGrid() {
      const [playersMove, setPlayersMove] = useState(true)
      const [board, setBoard] = useState(initGame)
      
      const handlePlayersTurn = position => {
        // compute the symbol when you need it
        const value = playersMove ? 'X' : 'O'
        
        // update the board using the previous board state
        setBoard(b => b.toSpliced(position, 1, {
          position,
          value
        }))
        
        // update the current player
        setPlayersMove(p => !p)
      }
      
      // compute available positions
      const availablePositions = board.filter(c => !c.value)
        
      useEffect(() => {
        // if it's the player's turn or no more positions, skip
        if(playersMove || availablePositions.length === 0) return
        
        // find an open position
        const idx = Math.floor(Math.random() * availablePositions.length)
        
        // call handlePlayersTurn with open position found
        handlePlayersTurn(availablePositions[idx].position)
      }, [playersMove, availablePositions])
      
      return (
        <div className="board">
          {board.map(({ position, value }) => (
            <div
              {...!value && { onClick: () => handlePlayersTurn(position) }}
              className="cell" 
              key={position}>
              {value || '-'}
            </div>
          ))}
        </div>
      )
    };
    
    ReactDOM
      .createRoot(root)
      .render(<TicTacToeGrid />)
    .board {
      display: grid;
      height: 50vmin;
      width: 50vmin;
      grid-template-rows: repeat(3, 1fr);
      grid-template-columns: repeat(3, 1fr);
    }
    
    .cell {
      display: flex;
      border: 1px solid black;
      justify-content: center;
      align-items: center;
    }
    <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    
    <div id="root"></div>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search