Hiken No Ace
Hiken No Ace

Reputation: 43

How can I deal with setState being asynchronous?

I am trying to make a basic hangman game and here is my state :

static defaultProps = {
    maxTries: 6,// maximum incorrect tries 
    imgs: [jpg0, jpg1, jpg2, jpg3, jpg4, jpg5, jpg6], //images changing through incorrect guesses
    words: [
      "donkey",
      "apple",
      "money",
      "drink",
      "sleep",
      "legend",
      "achievement"
    ]// list of words to be picked randomly
  };
 this.state = {
      guessesNum: 0,//saves number of incorrect guesses
      answer: "",//
      guessed: [],//adds characters user typed if the answer contains
      gameOver: false // checks if game is over 
    };

I have created a method gameSetup which will pick a random word from the array of words and set the answer also guessed in beginning is _ _ _ _ _ _ so when user makes a correct guess it is replaced

 componentDidMount() {
    this.gameSetup();
  }
  gameSetup = () => {
    let word = this.props.words[
      Math.floor(Math.random() * this.props.words.length)
    ];
    console.log(word);
    let guessedArray = [];
    for (let i = 0; i < word.length; i++) {
      guessedArray.push("_");
    }
    this.setState(st => {
      return { answer: word, guessed: guessedArray };
    });
  };

The problem is when user is making guesses lets say the answer is apple
and the guessed array becomes ['a','p','p','l','e']; and I am checking if

guessed.join('')===answer then gameOver:true

but even if they are the same game over is being changed to true only on next try , because it is checking the if condition before actually setState has finished since it is asynchronous

This is how I am updating the state

userGuess = char => {
    let indexes = [];
    for (let i = 0; i < this.state.answer.length; i++) {
      if (this.state.answer[i] === char) {
        indexes.push(i);
      }
    }
    if (indexes.length === 0) {
      this.setState(st => {
        return { guessesNum: st.guessesNum + 1 };
      });
    } else {
      indexes.forEach(ind => {
        this.setState(st => {
          return { guessed: this.updateArray(st.guessed, ind, char) };
        });
      });
    }
    if (this.state.guessed.join("") === this.state.answer) {
      this.setState({ gameOver: true });
    }
  };

This is a method to update array

  updateArray = (array, index, value) => {
    array[index] = value;
    return array;
  };

How can I make it ask the condition only after the state is updated ?

Here is the JSX

render() {
    return (
      <div>
        <img src={this.props.imgs[this.state.guessesNum]} alt="0" />
        <div
          style={{
            display: "flex",
            width: "10%",
            justifyContent: "space-evenly",
            margin: "0 auto"
          }}
        >
          {this.state.guessed.map((char, index) => {
            return <p key={index}>{char}</p>;
          })}
        </div>
        <div className="letters">
          <Button guess={this.userGuess} />
        </div>
      </div>
    );
  }

And this is the Button component (acutally it holds all Buttons)

import React, { Component } from "react";

export default class Button extends Component {
  handleGuess = e => {
    this.props.guess(e.target.textContent);
  };
  render() {
    return (
      <div>
        <button onClick={this.handleGuess}>a</button>
        <button onClick={this.handleGuess}>b</button>
        <button onClick={this.handleGuess}>c</button>
        <button onClick={this.handleGuess}>d</button>
        <button onClick={this.handleGuess}>e</button>
        <button onClick={this.handleGuess}>f</button>
        <button onClick={this.handleGuess}>g</button>
        <button onClick={this.handleGuess}>h</button>
        <button onClick={this.handleGuess}>i</button>
        <button onClick={this.handleGuess}>j</button>
        <button onClick={this.handleGuess}>k</button>
        <button onClick={this.handleGuess}>l</button>
        <button onClick={this.handleGuess}>m</button>
        <button onClick={this.handleGuess}>n</button>
        <button onClick={this.handleGuess}>o</button>
        <button onClick={this.handleGuess}>p</button>
        <button onClick={this.handleGuess}>q</button>
        <button onClick={this.handleGuess}>r</button>
        <button onClick={this.handleGuess}>s</button>
        <button onClick={this.handleGuess}>t</button>
        <button onClick={this.handleGuess}>u</button>
        <button onClick={this.handleGuess}>v</button>
        <button onClick={this.handleGuess}>w</button>
        <button onClick={this.handleGuess}>x</button>
        <button onClick={this.handleGuess}>y</button>
        <button onClick={this.handleGuess}>z</button>
      </div>
    );
  }
}

Upvotes: 1

Views: 56

Answers (1)

Clarity
Clarity

Reputation: 10873

setState acepts a second param, which is a callback function invoked after state is changed:

 this.setState(st => {
   return {
     guessesNum: st.guessesNum + 1
   };
 }, () => {
   if (this.state.guessed.join("") === this.state.answer) {
     this.setState({
       gameOver: true
     });
   }
 });

Upvotes: 4

Related Questions