简体   繁体   中英

Trouble with React not updating state (hooks)

I have a small memory game that I am building to learn React. The idea is that you click a tile, it is shuffled, and you try to pick one you haven't already selected. The problem is I can't seem to get my internal state for which tiles have previously been seen to update.

Here is my component:

const Gameboard = (props) => {
  const [tiles, setTiles] = useState([]);
  const [currentScore, setCurrentScore] = useState(0);
  const [highScore, setHighScore] = useState(0);
  const [previouslySeen, setPreviouslySeen] = useState([]);

  const handleClick = (evt) => {
    const tile = evt.target;
    if (!previouslySeen.includes(tile)) {
      setCurrentScore(current => current + 1);
      props.setCurrentScore(current => current + 1);
      setPreviouslySeen([...previouslySeen, tile]);
    } else {
      alert("Game over, thanks for playing!");
      if (currentScore > highScore) {
        setHighScore(currentScore);
        props.setHighScore(currentScore);
      }
      setCurrentScore(0);
      props.setCurrentScore(0);
      setPreviouslySeen([]);
    }
  };

  const generateTiles = (tileCount) => {
    return [...Array(tileCount).keys()].map((n) => {
      return <Tile key={n} number={n} handleClick={handleClick} />;
    });
  };

  const shuffleTiles = (tiles) => {
    let tilesCopy = [...tiles];

    for (let i = tilesCopy.length - 1; i > 0; i--) {
      const j = Math.floor(Math.random() * i);
      const temp = tilesCopy[i];
      tilesCopy[i] = tilesCopy[j];
      tilesCopy[j] = temp;
    }

    return tilesCopy;
  };

  useEffect(() => {
    const initialTiles = generateTiles(props.tileCount);
    setTiles(shuffleTiles(initialTiles));
  }, []);

  useEffect(() => {
    setTiles((oldTiles) => {
      const newTiles = shuffleTiles(oldTiles);
      return newTiles;
    });
  }, [currentScore, highScore]);

  return <div className="Gameboard">{tiles}</div>;
};

The handleClick method gets passed into the individual Tile components and is just called with onClick . The handleClick method is what is giving me trouble, it seems as if it doesn't ever update previouslySeen`, and I have similar problems with setting the high score in the parent component so that it is reflected in the UI. How are you supposed to handle updates like this in React?

Try this:

const handleClick = (evt) => {
const tile = evt.target;
if (!previouslySeen.includes(tile)) {
  setCurrentScore(current => current + 1);
  props.setCurrentScore(current => current + 1);
  // Change the following line:
  setPreviouslySeen(previousState => [...previousState, tile]);
} else {

Also, you're getting setCurrentScore, setHighScore from parent props and from declaring states:

  const [currentScore, setCurrentScore] = useState(0);
  const [highScore, setHighScore] = useState(0);

So essentially you're keeping two separate currentScore and highScore states - one in the Gameboard component, and one in its parent. It's best to delete these state variables and setters from Gameboard and get them directly from parent props.

It turned out that it was because I was storing JSX elements directly in state. Removing that and instead just storing the tile value (plain JS strings) fixed the issue where state wasn't being correctly updated. Quite the gotcha!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM