state not updating when using react state hook within setinterval

  • Last Update :
  • Techknowledgy :

The reason is because the callback passed into setInterval's closure only accesses the time variable in the first render, it doesn't have access to the new time value in the subsequent render because the useEffect() is not invoked the second time. Then, every time setInterval ticks, it will actually call setTime(time + 1), but time will always hold the value it had initially when the setInterval callback (closure) was defined. The performance impact of setTimeout is insignificant and can be generally ignored. Unless the component is time-sensitive to the point where newly set timeouts cause undesirable effects, both setInterval and setTimeout approaches are acceptable.

This is our Splunktool team suggestion ✌, we tried and its working fine
const [state = initialValue, setState] = useState()
2._
function Clock() {
  const [time, setTime] = React.useState(0);
  React.useEffect(() => {
    const timer = window.setInterval(() => {
      setTime(prevTime => prevTime + 1); // <-- Change this line!
    }, 1000);
    return () => {
      window.clearInterval(timer);
    };
  }, []);

  return (
    <div>Seconds: {time}</div>
  );
}

ReactDOM.render(<Clock />, document.querySelector('#app'));
3._
<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>

You can use the alternative form of useState's setter and provide a callback rather than the actual value you want to set (just like with setState):

setTime(prevTime => prevTime + 1);
6._
function useInterval(callback, delay) {
  const intervalRef = React.useRef();
  const callbackRef = React.useRef(callback);

  // Remember the latest callback:
  //
  // Without this, if you change the callback, when setInterval ticks again, it
  // will still call your old callback.
  //
  // If you add `callback` to useEffect's deps, it will work fine but the
  // interval will be reset.

  React.useEffect(() => {
    callbackRef.current = callback;
  }, [callback]);

  // Set up the interval:

  React.useEffect(() => {
    if (typeof delay === 'number') {
      intervalRef.current = window.setInterval(() => callbackRef.current(), delay);

      // Clear interval if the components is unmounted or the delay changes:
      return () => window.clearInterval(intervalRef.current);
    }
  }, [delay]);
  
  // Returns a ref to the interval ID in case you want to clear it manually:
  return intervalRef;
}


const Clock = () => {
  const [time, setTime] = React.useState(0);
  const [isPaused, setPaused] = React.useState(false);
        
  const intervalRef = useInterval(() => {
    if (time < 10) {
      setTime(time + 1);
    } else {
      window.clearInterval(intervalRef.current);
    }
  }, isPaused ? null : 1000);

  return (<React.Fragment>
    <button onClick={ () => setPaused(prevIsPaused => !prevIsPaused) } disabled={ time === 10 }>
        { isPaused ? 'RESUME ⏳' : 'PAUSE 🚧' }
    </button>

    <p>{ time.toString().padStart(2, '0') }/10 sec.</p>
    <p>setInterval { time === 10 ? 'stopped.' : 'running...' }</p>
  </React.Fragment>);
}

ReactDOM.render(<Clock />, document.querySelector('#app'));

Suggestion : 2

The actual “problem” here is that the function passed to setInterval is created just once at the start. Looks good, right. But, here’s the thing - the function passed to setInterval is defined once and it closes over the old stale value of state, which has not yet updated. So, the function passed to setInterval is created just one time when you call it. That means, while clearing the interval, it always considered the value of ID to be 0 (which was the initial state when we created the function passed to setInterval). In our case, the only thing that we absolutely need to put in useEffect is the logic for clearing the interval. We’ll just put that and nothing else.

1._
// For storing the intervalID when we create itconst [intervalID, setIntervalID] = useState(0);
// For starting the interval ->useEffect(() => {    let myIntervalID = setInterval(myFunction, 5000);    setIntervalID(myIntervalID);  }, []);
// The function that makes the callconst myFunction = () => {    // make the call and get back the result    if(result === false){        // Here, I want to clear the interval        clearInterval(intervalID);        // Navigate the user to other screen after the interval is cleared    }}
2._
const [intervalID, setIntervalID] = useState(0);
const [shouldIntervalBeCancelled, setShouldIntervalBeCancelled] = useState(false);
// For starting the interval ->useEffect(() => {    let myIntervalID = setInterval(myFunction, 5000);    setIntervalID(myIntervalID);  }, []);
useEffect(() => {
         if (shouldIntervalBeCancelled) {
            clearInterval(myIntervalID); // this being inside a useEffect makes sure that it gets the fresh value of state    }  }, [shouldIntervalBeCancelled]);
            // The function that makes the callconst myFunction = () => {    // make the call and get back the result    if(result === "someValue"){        // Here, I want to clear the interval, I just set shouldIntervalBeCancelled to be true        setShouldIntervalBeCancelled(true);        // Navigate the user to other screen after the interval is cleared    }}

Suggestion : 3

In React, when you try to update state inside setInterval() like this, it wouldn’t work. Trying to put it outside of useEffect() doesn’t make any difference. This can be used to update the state inside setInterval() But in this way, useEffect() gets messy every time the variables we want to use increase. So me personally, I prefer to keep using functional updates if I only need to update state.

1._
import React, { useEffect, useState } from 'react'

export const Sample = () => {
  const [count, setCount] = useState(0)
  
  useEffect(() => {
    const timer = setInterval(() => {
      console.log('ticking')
      setCount(count + 1)
    }, 1000)
    
    return () => clearInterval(timer)
  }, [])
  
  
  return <div>count is: {count}</div>
}
2._
import React, { useEffect, useState } from 'react'

export const Sample = () => {
  let timer
  const [count, setCount] = useState(0)
  
  const updateCount = () => {
    timer = !timer && setInterval(() => {
      console.log('ticking')
      setCount(count + 1)
    }, 1000)
  }
  
  useEffect(() => {
    updateCount()
    
    return () => clearInterval(timer)
  }, [])
  
  
  return <div>count is: {count}</div>
}
3._
() => {
   console.log('ticking')
   setCount(count + 1)
}
5._
import React, { useEffect, useState } from 'react'

export const Sample = () => {
  let timer
  const [count, setCount] = useState(0)
  
  const updateCount = () => {
    timer = !timer && setInterval(() => {
      console.log('ticking')
      setCount(prevCount => prevCount + 1)
    }, 1000)
    
+   if (count === 3) {     // new
+     console.log('stop!')
+     clearInterval(timer)
+   }
  }
  
  useEffect(() => {
    updateCount()
    
    return () => clearInterval(timer)
  }, [])
  
  
  return <div>count is: {count}</div>
}
6._
setCount(prevCount => {
   prevCount + 1

   if (prevCount + 1 === 3) {
      // do something
   }
})

Suggestion : 4

React this.setState, and useState does not make changes directly to the state object. React this.setState, and React.useState create queues for React core to update the state object of a React component. Let’s dive into why this.setState and React.useStatedo not update immediately. Does it feel like when you call this.setState or React.useState, the changes feel like it’s a step behind?

If you’re using a class component, you will have to usethis.setState() to update the state of a React component.

this.setState(state, callback);

The second parameter this.setState() accepts is the callback function, and that’s where you’ll want to add your side effects.

This callback function will get triggered when React state has finished updating.

this.setState(newStateObject, () => {
   // ... do some other actions
});

The example above uses the arrow function, but you can do it with a traditional function syntax.

this.setState(newStateObject, function() {
   // ... do some other actions
});

That hook function will only activate if the values in the list change.

Let’s take a look at an example

let s;

const Foo = () => {
  const [counter, setCounter] = React.useState(0);

  // Emmulate componentDidMount lifecycle
  React.useEffect(() => {
    s = setInterval(() => {
      setCounter(state => (state +1));
    }, 1000);
  }, []);

  // This is for counter state variable
  React.useEffect(() => {
    if (counter > 9) {
      clearInterval(s);
    }
  }, [counter]);

  return <span>{counter}</span>;
};

Suggestion : 5

The code above schedules a new interval to run every second inside of the useEffect Hook. This will schedule once the React component mounts for the first time. To properly clear the interval, we return clearInterval from the useEffect Hook, passing in the interval. For example, the code below schedules a new interval when the React component mounts for the first time. After the React component unmounts the interval is cleared: The useEffect function returns the clearInterval method with the scheduled interval passed into it. As a result, the interval is correctly cleared and no longer triggers every second after the component unmounts from the DOM.

1._
useEffect(() => {
   const interval = setInterval(() => {
      console.log('This will run every second!');
   }, 1000);
   return () => clearInterval(interval);
}, []);
2._
setInterval(() => {
   console.log('Interval triggered');
}, 1000);
3._
...

useEffect(() => {
   const interval = setInterval(() => {
      setSeconds(seconds => seconds + 1);
   }, 1000);
   return () => clearInterval(interval);
}, []);

...

Suggestion : 6

Simple enough; the intent behind the above code is to update our “counter” state variable every so often, and reflect that in the UI. You’d quickly end up running into weird behavior: Since “counter” is changed by setInterval, we need useEffect to realize a change has occurred and re-run the setInterval function, this time feeding it the new, updated value of “counter”. So we should add “counter” to our list of dependencies: The preferred solution would be instead to use functional updates in useState. Rather than directly passing a variable/object to our useState updater (changeCounter in our case), we can pass a function, which takes as an argument the previous value of counter:

1._
import React, { useState } from "react";
import "./styles.css";

export default function App() {
  const [counter, changeCounter] = useState(0);

  setInterval(() => {
    changeCounter(counter + 1);
  }, 10000);

  return (
    <div className="App">
      <h1>DVAS0004 setInterval()</h1>
      <h2>Sandbox counter: {counter}</h2>
    </div>
  );
}
2._
import React, { useState, useEffect } from "react";
import "./styles.css";

export default function App() {
  const [counter, changeCounter] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      changeCounter(counter + 1);
    }, 10000);

    return () => clearInterval(interval)
  }, []);

  return (
    <div className="App">
      <h1>DVAS0004 setInterval()</h1>
      <h2>Sandbox counter: {counter}</h2>
    </div>
  );
}

3._
import React, { useState, useEffect } from "react";
import "./styles.css";

export default function App() {
  const [counter, changeCounter] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      changeCounter(counter + 1);
    }, 1000);

    return () => clearInterval(interval);
  }, [counter]); ///<--- this right here

  return (
    <div className="App">
      <h1>DVAS0004 setInterval()</h1>
      <h2>Sandbox counter: {counter}</h2>
    </div>
  );
}


Suggestion : 7

I’m quite new to React, so my problem might be quite simple to fix. I’m building a snake game, where the snake and current direction are saved as state. A start button sets up the interval for updating the snake depending on the current direction, but … snake doesn’t want to update. I’m sure I’m missing something very basic. Here’s the relevant parts of code: I think that the reason why your function never updates is that your closure is always referencing the first render, so snake position is always the same. Awesome, that article addresses my problem exactly. I’ll need some time to get my head around it but I’m sure I can solve the issue now.

Hi people,

function Game(){

    const size = 15;

    const [snake, setSnake] = useState([95,94,93]);
    const [dir, setDir] = useState('right');
    const [gameInterval, setGameInterval] = useState([]);
    const [isGameRunning, setIsGameRunning] = useState(false);

    // add keyboard event listener on first render
    useEffect(()=>{
        document.addEventListener('keyup', handleKeyUp);
        return ()=>document.removeEventListener('keyup', handleKeyUp)
    },[]);

    function handleKeyUp(e){
        let newDir;
        if (e.keyCode === 37){
            newDir = 'left';
        } else if (e.keyCode === 38){
            newDir = 'up';
        } else if (e.keyCode === 39){
            newDir = 'right';
        } else if (e.keyCode === 40){
            newDir = 'down';
        } else {
            return;
        };
        setDir(newDir);
    };


    // toggle game on/off
    function handleStartBtn(){
        if (!isGameRunning){
            setIsGameRunning(true);
            if (gameInterval.length === 0){

                // moveSnake gets called every 800ms,
                // but doesn't update the snake

                let interval = setInterval(moveSnake, 800);
                setGameInterval([...gameInterval, interval])
            }
        } else if (isGameRunning){
            setIsGameRunning(false);
            if (gameInterval.length !== 0){
                gameInterval.forEach(int => clearInterval(int));
                setGameInterval([]);
            }
        }
    }


    function moveSnake(){
        let newSnake = [...snake];

        console.log(newSnake)
        // this always logs the original position of the snake on every run

        let head = newSnake[0];
        newSnake.pop();
        if (dir === 'right'){
            head += 1;
        } else if (dir === 'left'){
            head -= 1;
        } else if (dir === 'up'){
            head -= size;
        } else if (dir === 'down'){
            head += size;
        };
        newSnake.unshift(head);

        console.log(newSnake)
        // this logs the correct new position of the snake

        setSnake(newSnake);
    }




    // render stuff

    const boardVals = {size, apple, snake};
    const btnDisplay = isGameRunning ? 'Stop' : 'Start';

    return (
        <div id="game">
            <Board vals={boardVals}/>
            <div>
       
                // I'm rendering state to check what's happening:
                // Here, snake gets updated correctly. But only once.

                {snake.map((segment,i) => <span key={i}>{segment} </span>)}
                <p>{dir}</p>

                <button className="btn" onClick={handleStartBtn}>{btnDisplay}</button>
            </div>
        </div>
    )
}

export default Game;

Suggestion : 8

The reason is because the callback passed into setInterval's closure only accesses the time variable in the first render, it doesn't have access to the new time value in the subsequent render because the useEffect() is not invoked the second time. So basically when you click a link, some Javascript runs that manipulates the URL in the address bar, without causing a page refresh, which in turn causes React Router to perform a page transition on the client-side. I'm trying out the new React Hooks and have a Clock component with a counter which is supposed to increase every second. However, the value does not increase beyond one.

1._
function Clock() {
  const [time, setTime] = React.useState(0);
  React.useEffect(() => {
    const timer = window.setInterval(() => {
      setTime(time + 1);
    }, 1000);
    return () => {
      window.clearInterval(timer);
    };
  }, []);

  return (
    <div>Seconds: {time}</div>
  );
}

ReactDOM.render(<Clock />, document.querySelector('#app'));
2._
<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>
3._
function Clock() {
  const [time, setTime] = React.useState(0);
  React.useEffect(() => {
    const timer = window.setInterval(() => {
      setTime(prevTime => prevTime + 1); // <-- Change this line!
    }, 1000);
    return () => {
      window.clearInterval(timer);
    };
  }, []);

  return (
    <div>Seconds: {time}</div>
  );
}

ReactDOM.render(<Clock />, document.querySelector('#app'));