How to compare state variables during function execution?

Viewed 46

I am attempting to simulate the roll of two six sided dice and show the dice rolling on screen by switching image sources. I intend for the dice to continue rolling until there is no tie. The problem is that the while loop is always true and loops infinitely because the states do not change until after slowRoll is executed. Can this be done with promises or what is the best way to achieve this?

  const [topRoll, setTopRoll] = useState(7);
  const [botRoll, setBotRoll] = useState(7);

  const rollDiceSix = () => {
    setTopRoll(Math.floor(Math.random() * 6) + 1);
    setBotRoll(Math.floor(Math.random() * 6) + 1);
  };

  const slowRoll = async () => {
    await new Promise((resolve) => {
      setTimeout(function () {
        resolve(rollDiceSix());
      }, 250);
    });
    while (topRoll === botRoll) {
      rollDiceSix();
    }
  };
1 Answers

Instead of using a loop, you could organize your code like this and call the roll function resursively if both values match:

const App = () => {  
  const [rollState, setRollState] = React.useState({
    top: undefined,
    buttom: undefined,
    rolling: false,
  });

  const rollDices = React.useCallback(() => {
    setRollState((prevState) => ({ ...prevState, rolling: true }));
    
    setTimeout(() => {    
      const rollTop = Math.floor(Math.random() * 6) + 1;
      const rollBottom = Math.floor(Math.random() * 6) + 1;
      const keepRolling = rollTop === rollBottom;
      
      setRollState({
        top: rollTop,
        bottom: rollBottom,
        rolling: keepRolling,
      });
      
      // Instead of a loop, we use recursion here:    
      if (keepRolling) rollDices();
    }, 500);  
  }, []);
  
  const diceClassName = `dice${ rollState.rolling ? ' rolling' : '' }${ rollState.top === rollState.bottom ? ' same' : '' }`;
  
  return (
    <div>
      <div className={ diceClassName }>{ rollState.top }</div>
      <div className={ diceClassName }>{ rollState.bottom }</div>
      <button disabled={ rollState.rolling } onClick={ rollDices }>
         Roll Again
      </button>
    </div>
  );
}

ReactDOM.render(<App />, document.querySelector('#app'));
body,
button {
  font-family: monospace;
}

body, p {
  margin: 16px;
}

.dice {
  width: 32px;
  height: 32px;
  display: flex;
  justify-content: center;
  align-items: center;
  border: 2px solid black;
  margin: 0 0 16px;
}

.rolling {
  background: yellow;
}

.same {
  background: red;
}

button {
  padding: 8px;
  border: 2px solid black;
  background: white;
  cursor: pointer;
  border-radius: 2px;
}

button:hover {
  background: cyan;
}

.as-console-wrapper {
    max-height: 67px !important;
}
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

<div id="app"></div>

The problem with your code is that slowRoll compares the previous values, not the ones that have just been set, as that function was created in the previous render cycle.

Also, it won't wait for the additional rolls, so you won't see the roll where you got the same values.

You can fix it using useEffect:

const App = () => {  
  const [topRoll, setTopRoll] = React.useState();
  const [bottomRoll, setBottomRoll] = React.useState();

  const rollDiceSix = React.useCallback(() => {
    const topRoll = Math.floor(Math.random() * 6) + 1;
    const bottomRoll = Math.floor(Math.random() * 6) + 1;
    
    setTopRoll(topRoll);
    setBottomRoll(bottomRoll);
    
    return topRoll === bottomRoll;
  }, []);

  const slowRoll = React.useCallback(() => {
    setTimeout(() => {
      rollDiceSix();
    }, 250);
  }, [rollDiceSix]);
  
  React.useEffect(() => {
    if (topRoll !== undefined && topRoll === bottomRoll) slowRoll();
  }, [topRoll, bottomRoll, slowRoll]);
  
  const diceClassName = `dice${ topRoll === bottomRoll ? ' same' : '' }`;
  
  return (
    <div>
      <div className={ diceClassName }>{ topRoll }</div>
      <div className={ diceClassName }>{ bottomRoll }</div>
      <button onClick={ slowRoll }>
         Roll Again
      </button>
    </div>
  );
}

ReactDOM.render(<App />, document.querySelector('#app'));
body,
button {
  font-family: monospace;
}

body, p {
  margin: 16px;
}

.dice {
  width: 32px;
  height: 32px;
  display: flex;
  justify-content: center;
  align-items: center;
  border: 2px solid black;
  margin: 0 0 16px;
}

.same {
  background: red;
}

button {
  padding: 8px;
  border: 2px solid black;
  background: white;
  cursor: pointer;
  border-radius: 2px;
}

button:hover {
  background: cyan;
}

.as-console-wrapper {
    max-height: 67px !important;
}
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

<div id="app"></div>

Related