Ali Aliakbar
Ali Aliakbar

Reputation: 33

Problem with react useState falling behind one step

i'm trying to convert a Javascript game which i learned from Udemy to a react application.its called Pig-Game apparently. you can roll the dice and add its value to your bank until you hit 1, then its your opponent's turn to roll the dice. you can hold the value and add it to your total before losing your turn. i have written a function for "btn-roll" to update the dice and add its value to my states. here is the code (excluded unnecessary parts ):

const App = () => {

  const [dice, setdice] = useState(null);
  const [current, setCurrent] = useState(0);
  const [activePlayer, setactivePlayer] = useState(0);

  const diceHandler = () => {
    if (dice !== 1) {
      setdice(() => {
        const _dice = (Math.floor(Math.random() * 6) + 1);
        setCurrent(current + _dice);
        console.log(_dice);
        return _dice;
      }
      );
    } else {
      activePlayer === 0 ? setactivePlayer(1) : setactivePlayer(0);
      setdice(() => {
        setCurrent(0);
        return null;
      })
    }
  }

 return (

      <button className="btn-roll" onClick={diceHandler}><i className="ion-ios-loop"></i>Roll dice</button>
      {dice ?
        <img src={require(`./dice-${dice}.png`)} alt="Dice" className="dice" /> :
        <></>
      }
  );
} 

first problem was that the 'dice' and 'current' falling behind 1 step. every time i clicked the button it showed the a random dice picture but the actual value for dice and current was 1 step behind. i fixed it with the code you are currently witnessing. now the problem is that once i hit "1" it adds the value to current, then i have to click again so the 'setCurrent(0)' gets initiated and activePlayer gets switched. i tried this function too but the result is the same :

  const diceHandler = () => {

    setdice(() => {
      if (dice !== 1) {
        const _dice = (Math.floor(Math.random() * 6) + 1);
        setCurrent(current + _dice);
        console.log(_dice);
        return _dice;
      } else {
        activePlayer === 0 ? setactivePlayer(1) : setactivePlayer(0);
        setdice(() => {
          setCurrent(0);
          return null;
        })
      }
    }
    );

  }

i want to know that if there is a better solution to my existing function ( for updating states ) and why do i have to click the button again. am i using the Hooks completely wrong ? thanks in advance

Upvotes: 1

Views: 137

Answers (2)

Rishabh Chaturvedi
Rishabh Chaturvedi

Reputation: 46

I hope the below code helps you!

 const App = () => {
      const [dice, setdice] = useState(null);
      const [current, setCurrent] = useState(0);
      const [activePlayer, setactivePlayer] = useState(0);

    const stateUpdater = _dice => {
      console.log(dice);
      if (dice !== 1) {
        setCurrent(current + _dice);
        console.log("dice value " + _dice);
      } else {
        console.log("Dice value is now one!", dice);
        activePlayer === 0 ? setactivePlayer(1) : setactivePlayer(0);
        setCurrent(0);
        setdice(null);
      }
    };

    const diceHandler = () => {
      let _dice = Math.floor(Math.random() * 6) + 1;
      setdice(_dice, stateUpdater(dice));
    };

    return (

     <button className="btn-roll" onClick={diceHandler}><i className="ion-ios-loop"></i>Roll dice</button>
     {dice ?
      <img src={require(`./dice-${dice}.png`)} alt="Dice" className="dice" /> :
    <></>
  }
  );
};

The diceHandler function is only responsible for generating new value of dice and updating it to the dice hook.

As soon as the hook is updated, a callback will be called, and its responsibility will be to update the value wherever needed and to progress game further.

The reason it was not working earlier is that setState functions are asynchronous and have delays. That's why the value of updated variable may not reflect immediately in the statements following the setState.

Upvotes: 1

Vincent Ramdhanie
Vincent Ramdhanie

Reputation: 103135

You might be able to simplify that logic a bit.

 const diceHandler = () => {
   const _dice = Math.floor(Math.random() * 6) + 1;
   
   if (_dice !== 1) {
     setCurrent(current + _dice);
     setdice(_dice);
   } else {
     setactivePlayer(activePlayer === 0 ? 1 : 0);
     setCurrent(0);
     setdice(0);
   }
 };

Note how the ternary operator is used to update the activePlayer. Using a callback function in the state setter is necessary sometimes but did not seem necessary here.

Upvotes: 2

Related Questions