useState asynchronous issue

Hi All,

I’m learning React Hooks and know useState is asynchrounous but how do i get this piece of code to show the letter immediately? I’ve already tried adding the function in useEffect, although I’m not completely sure if this is correct.

When i click on the correct letter it show the letter only after clicking on another (wrong letter).

function guessedWord() {
    // Show guessed letter or "_"
    let answ = answer.split("").map((ltr) => {
      return guessed.has(ltr) ? ltr : "_";
    });
 
    return answ;
  }
 
  useEffect(() => {
    // setGuessed(guessed);
    guessedWord();
  }, [guessedWord]);

This is the whole piece of code:

import React, { useState, useEffect } from "react";

import "../Hangman.css";
import { randomWord } from "../words";
import img0 from "../img/0.jpg";
import img1 from "../img/1.jpg";
import img2 from "../img/2.jpg";
import img3 from "../img/3.jpg";
import img4 from "../img/4.jpg";
import img5 from "../img/5.jpg";
import img6 from "../img/6.jpg";

const Hangman = (props) => {
  let [nWrong, setNwrong] = useState(0);
  let [guessed, setGuessed] = useState(new Set());
  const [answer, setAnswer] = useState("apple");

  function guessedWord() {
    // Show guessed letter or "_"
    let answ = answer.split("").map((ltr) => {
      return guessed.has(ltr) ? ltr : "_";
    });

    return answ;
  }

  useEffect(() => {
    // setGuessed(guessed);
    guessedWord();
  }, [guessedWord]);

  const handleGuess = (e) => {
    // Grab clicked ltr
    let ltr = e.target.value;
    setGuessed(guessed.add(ltr));
    setNwrong(nWrong + (answer.includes(ltr) ? 0 : 1));
    console.log(ltr);
  };

  const generateButtons = () => {
    let alphabet = "abcdefghijklmnopqrstuvwxyz";
    return alphabet.split("").map((ltr, idx) => (
      <button
        onClick={handleGuess}
        value={ltr}
        disabled={guessed.has(ltr)}
        key={idx}
      >
        {ltr}
      </button>
    ));
  };

  // Show winner
  function showWinner() {
    let gameState = generateButtons();
    const gameOver = nWrong >= props.maxWrong;
    const isWinner = guessedWord().join("") === answer;

    if (gameOver) gameState = "You lose!";
    if (isWinner) gameState = "You Win!";

    return gameState;
  }

  // Restart button
  const restartGame = () => {
    setNwrong(0);
    setGuessed(new Set());
    setAnswer("apple");
  };

  return (
    <div className="Hangman">
      <div className="Hangman__header1__img">
        <h1>Hangman</h1>
        <img src={props.images[nWrong]} alt="" />
      </div>

      <div className="Hangman__words__btns">
        <div className="Hangman__words__btns__wrapper">
          <h2>Wrong guesses: {nWrong}</h2>
          <p className="Hangman__word">{guessedWord()}</p>

          <div>{showWinner()}</div>
        </div>

        <div className="hangman__restart--btn">
          <button onClick={restartGame}>Restart Game</button>
        </div>
      </div>
    </div>
  );
};

Hangman.defaultProps = {
  maxWrong: 6,
  images: [img0, img1, img2, img3, img4, img5, img6],
};

export default Hangman;

It looks like your guessedWord() function returns an array, while you probably want it to return a string

Hi Snigo, thanks for taking the time to reply. It should return an array bc this can than be compared between the guessed Set and answer which has been split into an array. If the Set .has(ltr) it outputs that letter, if not it’s greyed out.

Everything works fine except when clicking a correct letter. This is bc useState works asynchronously and outputs the correct letter on update state. I think it can be fixed with useEffect but not sure how.

The exact same code, without the Hooks ofc, works perfectly fine in a good old class component. It’s just I want it to work with Hooks :sweat_smile:

This is a problem. Button doesn’t have value attribute, you might want instead check e.target.textContent instead.

UPDATE: *Unless you specifically set value, which you did

Ok, so the problem is somewhat hidden. React compares states and do not trigger re-render if same. Consider this:

const stateA = new Set();
const stateB = stateA.add('a');
const stateC = new Set(stateA.add('a'));

console.log(stateA === stateB); // true *BAD
console.log(stateA === stateC); // false *GOOD

Thanks snigo. From the React docs, this pattern is best avoided: https://reactjs.org/docs/hooks-faq.html#is-there-something-like-forceupdate