Selective state not updating in React component

I'm trying to create a basic snake game in React. I seem to be misunderstanding state. Right now the player can move, and the cherry gets placed for pickup, however when checking for collision inside of the move() function, I can't seem to update gameOver state. Is there something obvious I am missing? Appreciate you taking the time to read and respond.

import { useEffect, useState } from "react";

const Snake = () => {
  const timer = (ms) => new Promise((res) => setTimeout(res, ms));

  let [gameStarted, setGameStarted] = useState(false);
  let [gameOver, setGameOver] = useState(false);

  let [playerLocation, setPlayerLocation] = useState({ x: 20, y: 20 });
  let [cherryLocation, setCherryLocation] = useState({});

  let direction = "W";

  const tableWidth = 40;
  const tableHeight = 40;
  const cellSize = "10px";

  const handleKeyPress = (e) => {
    switch (e.code) {
      case "ArrowLeft":
        direction = "W";
        break;
      case "ArrowRight":
        direction = "E";
        break;
      case "ArrowUp":
        direction = "N";
        break;
      case "ArrowDown":
        direction = "S";
        break;
    }
  };

  function getRandomInt(min, max) {
    min = Math.ceil(min);
    max = Math.floor(max);
    return Math.floor(Math.random() * (max - min) + min);
  }

  const placeCherry = () => {
    return {
      x: getRandomInt(0, tableWidth),
      y: getRandomInt(0, tableHeight)
    };
  };

  const checkCollision = () => {
    if (playerLocation.x <= 0 || playerLocation.x >= tableWidth || playerLocation.y <= 0 || playerLocation.y >= tableHeight) {
      return true
    }
    return false
  };

  const startGame = () => {
    if (!gameStarted) {
      setGameStarted(!gameStarted);
      setGameOver(!gameOver);
      update();
    }
  };

  const move = () => {
    let newX = playerLocation.x;
    let newY = playerLocation.y;
    switch (direction) {
      case "N":
        newY = playerLocation.y -= 1;
        break;
      case "S":
        newY = playerLocation.y += 1;
        break;
      case "E":
        newX = playerLocation.x += 1;
        break;
      case "W":
        newX = playerLocation.x -= 1;
        break;
    }
    if(checkCollision()) {
      setGameOver(!gameOver);
    }
    setPlayerLocation((prevState) => ({
      ...prevState,
      x: newX,
      y: newY
    }));
  };

  const update = async () => {
    setCherryLocation(placeCherry());
    while (!gameOver) {
      console.log(gameOver);
      await timer(100);
      move();
    }
    alert("Game Over!");
  };

  
  const renderBoard = () => {
    return (
      <table style={{ border: "2px solid rgba(0, 0, 0, 0.05)" }}>
        <tbody>
          {[...Array(tableHeight)].map((y, i) => {
            return (
              <tr key={i}>
                {[...Array(tableWidth)].map((x, j) => {
                  if (playerLocation.y === i && playerLocation.x === j) {
                    return (
                      <td
                        style={{
                          width: cellSize,
                          height: cellSize,
                          backgroundColor: "green"
                        }}
                        key={j}
                      >
                        {" "}
                      </td>
                    );
                  }
                  if (cherryLocation.y === i && cherryLocation.x === j) {
                    return (
                      <td
                        style={{
                          width: cellSize,
                          height: cellSize,
                          backgroundColor: "pink"
                        }}
                        key={j}
                      >
                        {" "}
                      </td>
                    );
                  }
                  return (
                    <td
                      style={{
                        width: cellSize,
                        height: cellSize,
                        backgroundColor: "black"
                      }}
                      key={j}
                    >
                      {" "}
                    </td>
                  );
                })}
              </tr>
            );
          })}
        </tbody>
      </table>
    );
  };

  useEffect(() => {
    window.addEventListener("keydown", handleKeyPress);

    return () => {
      window.removeEventListener("keydown", handleKeyPress);
    };
  }, []);

  return (
    <div>
      <p>Snake</p>
      <div style={{ display: "flex", justifyContent: "center" }}>
        {renderBoard()}
      </div>
      <div>
        <button
          disabled={gameOver}
          style={{ width: "250px", height: "50px", marginTop: "30px" }}
          onClick={startGame}
        >
          Start
        </button>
      </div>
    </div>
  );
};

export default Snake;

Solution 1:

The gameOver state was not being updated inside the async function,which leads to an infinite while loop. So I tried to replace the timer with setInterval updater function.

Replace the existing functions with following to proceed.

 const timerIdRef = useRef();
    
    useEffect(() => {
      if (gameOver) {
        alert("Game Over!");
      }
    
      if (!gameOver && !checkCollision()) {
        setCherryLocation(placeCherry());
      }
    
      return () => clearInterval(timerIdRef.current);
    }, [gameOver]);
    
    const handleKeyPress = (e) => {
      switch (e.code) {
        case "ArrowLeft":
          direction = "W";
          update();
          break;
        case "ArrowRight":
          direction = "E";
          update();
          break;
        case "ArrowUp":
          direction = "N";
          update();
          break;
        case "ArrowDown":
          direction = "S";
          update();
          break;
      }
    };
    
    const startGame = () => {
      if (!gameStarted) {
        setGameStarted(true);
        setGameOver(false);
        update();
      }
    };
    
    const move = () => {
      let newX = playerLocation.x;
      let newY = playerLocation.y;
      switch (direction) {
        case "N":
          newY = playerLocation.y -= 1;
          break;
        case "S":
          newY = playerLocation.y += 1;
          break;
        case "E":
          newX = playerLocation.x += 1;
          break;
        case "W":
          newX = playerLocation.x -= 1;
          break;
      }
    
      if (checkCollision()) {
        setGameOver(true);
      }
      setPlayerLocation({
        x: newX,
        y: newY
      });
    };
    
    const update = () => {
      clearInterval(timerIdRef.current);
      if (!gameOver) {
        timerIdRef.current = setInterval(() => move(), 100);
      }
    };