javascriptreactjsstateupdatingeventhandler

Updating state twice when the button is clicked, React


I want to create simple 2048 game application. My initial board is composed of 16 array elements. The generate() function generates a value of '2' in a random empty element

It works just fine and creates one random '2' for me, but the problem starts when I want to call it twice in a handler like this:

  const handleNewGame = () => {
      generate()
      generate()
  }

I've read about prevState but have no idea how to implement it in this batch of code to work properly.

Here is my game component:

const width = 4;

const Game = () => {
  const [Board, setBoard] = useState([]);

  const createBoard = () => {
    let initialBoard = [];
    for (let i = 0; i < width * width; i++) {
      initialBoard.push("");
    }
    return initialBoard;
  };

  const generate = () => {
    let board = [...Board];
    let randomNumber = Math.floor(Math.random() * Board.length);
    console.log(randomNumber);
    if (board[randomNumber] === "") {
      board[randomNumber] = 2;
      setBoard(board);
    } else generate()
  };

  const handleNewGame = () => {
      generate()
      generate()
  }

  useEffect(() => {
    setBoard(createBoard);
    console.log(`Board Created!`);
  }, []);

  return (
    <div className="game-container">
      <button onClick={handleNewGame}>NewGame</button>
      <div className="game">
        {Board.map((value, index) => (
          <div className="tile" key={index}>
            {value}
          </div>
        ))}
      </div>
    </div>
  );
};

export default Game;

I'll be glad for the answer.


Solution

  • setState(), named setBoard() in your code is asynchronous, whatch this great video on the Event Loop for you to understand more: https://www.youtube.com/watch?v=8aGhZQkoFbQ.

    See if this will suit your needs:

    import { useEffect, useState } from 'react';
    
    const width = 4;
    
    const Game = () => {
        const [Board, setBoard] = useState([]);
    
        const createBoard = () => {
            let initialBoard = [];
            for (let i = 0; i < width * width; i++) {
                initialBoard.push('');
            }
            return initialBoard;
        };
    
        const randomBoardPosition = () => {
            return Math.floor(Math.random() * Board.length);
        };
    
        const generate = () => {
            let board = createBoard();
            let randomNumber = randomBoardPosition();
            let positionsFilled = 0;
    
            while (positionsFilled < 2) {
                if (board[randomNumber] === '') {
                    board[randomNumber] = 2;
                    positionsFilled++;
                } else {
                    randomNumber = randomBoardPosition();
                }
            }
            setBoard(board);
        };
    
        const handleNewGame = () => {
            generate();
        };
    
        useEffect(() => {
            setBoard(createBoard);
            console.log(`Board Created!`);
        }, []);
    
        return (
            <div className="game-container">
                <button onClick={handleNewGame}>NewGame</button>
                <div className="game">
                    {Board.map((value, index) => (
                        <div className="tile" key={index}>
                            {value}
                        </div>
                    ))}
                </div>
            </div>
        );
    };
    
    export default Game;