Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can not update state inside setInterval in react hook

I want to update state every second inside setinterval() but it doesn't work. I am new to react hook so can not understand why this is happening. Please take a look at the following code snippet and give me advice.

// State definition

const [gamePlayTime, setGamePlayTime] = React.useState(100);
let targetShowTime = 3;
.........................
// call function
React.useEffect(() => {
    gameStart();
  }, []);
.............

const gameStart = () => {
    gameStartInternal = setInterval(() => {
      console.log(gamePlayTime); //always prints 100
      if (gamePlayTime % targetShowTime === 0) {

        //can not get inside here

        const random = (Math.floor(Math.random() * 10000) % wp("70")) + wp("10");
        const targetPosition = { x: random, y: hp("90") };
        const spinInfoData = getspinArray()[Math.floor(Math.random() * 10) % 4];
        NewSpinShow(targetPosition, spinInfoData, spinSpeed);
      }
      setGamePlayTime(gamePlayTime - 1);
    }, 1000);
  };

like image 662
goldvenus Avatar asked Oct 15 '19 18:10

goldvenus


People also ask

How do you update state in setInterval in React?

Running setInterval in a React ComponentWe have the gamePlayTime state which we update by calling setGamePlayTime . Next, we call useEffect with a callback and create the timer inside the callback by calling setInterval . We pass in 1000 as the 2nd argument so that the setInterval callback only runs 1000 milliseconds.

How do you update state immediately in React hooks?

To update state in React components, we'll use either the this. setState function or the updater function returned by the React. useState() Hook in class and function components, respectively.

Can we update state directly in React?

One should never update the state directly because of the following reasons: If you update it directly, calling the setState() afterward may just replace the update you made. When you directly update the state, it does not change this.


2 Answers

The reason why you did not get updated state is because you called it inside useEffect(() => {}, []) which is only called just once.

useEffect(() => {}, []) works just like componentDidMount().

When gameStart function is called, gamePlaytime is 100, and inside gameStart, it uses the same value however the timer works and the actual gamePlayTime is changed. In this case, you should monitor the change of gamePlayTime using useEffect.

...
  useEffect(() => {
      if (gamePlayTime % targetShowTime === 0) {
        const random = (Math.floor(Math.random() * 10000) % wp("70")) + wp("10");
        const targetPosition = { x: random, y: hp("90") };
        const spinInfoData = getspinArray()[Math.floor(Math.random() * 10) % 4];
        NewSpinShow(targetPosition, spinInfoData, spinSpeed);
      }
  }, [gamePlayTime]);

  const gameStart = () => {
    gameStartInternal = setInterval(() => {
      setGamePlayTime(t => t-1);
    }, 1000);
  };
...
like image 172
Cool Dev Avatar answered Sep 22 '22 10:09

Cool Dev


You're creating a closure because gameStart() "captures" the value of gamePlayTime once when the useEffect hook runs and never updates after that.

To get around this, you must use the functional update pattern of React hook state updating. Instead of passing a new value directly to setGamePlayTime(), you pass it a function and that function receives the old state value when it executes and returns a new value to update with. e.g.:

setGamePlayTime((oldValue) => {
  const someNewValue = oldValue + 1;
  return someNewValue;
});

Try this (essentially just wrapping the contents of your setInterval function with a functional state update):

const [gamePlayTime, setGamePlayTime] = React.useState(100);
let targetShowTime = 3;

// call function
React.useEffect(() => {
    gameStart();
  }, []);

const gameStart = () => {
    gameStartInternal = setInterval(() => {
      setGamePlayTime((oldGamePlayTime) => {
        console.log(oldGamePlayTime); // will print previous gamePlayTime value
        if (oldGamePlayTime % targetShowTime === 0) {
          const random = (Math.floor(Math.random() * 10000) % wp("70")) + wp("10");
          const targetPosition = { x: random, y: hp("90") };
          const spinInfoData = getspinArray()[Math.floor(Math.random() * 10) % 4];
          NewSpinShow(targetPosition, spinInfoData, spinSpeed);
        }
        return oldGamePlayTime - 1;
      });
    }, 1000);
  };
like image 31
jered Avatar answered Sep 22 '22 10:09

jered