2

here is my code:

function Tiker() {
  var [count, setCount] = useState(0);

  useEffect(() => {
    var timerID = setInterval(_=>
      setCount(count=>count+1)//setCount(count+1) wont work
    , 1000);

    return function cleanup() {
      clearInterval(timerID);
    };
  }, []);
  return <div>
      this is ticker   
      <button onClick={() => 
        setCount(count + 1)//setCount(count+1) does work
      }>up </button>
      {count}
      </div>
}

By trial and error I discovered that if I use setCount from within setinterval callback, I have to pass a callback to the set state rather than just value.

its not the case if I call from onclick.

Why is that?

yigal
  • 3,923
  • 8
  • 37
  • 59

2 Answers2

2

The problem is the second argument of useEffect

useEffect(() => {
    var timerID = setInterval(_=>
        setCount(count=>count+1)//setCount(count+1) wont work
    , 1000);

    return function cleanup() {
        clearInterval(timerID);
    };
}, []);

It is empty array ([]). It defines list of dependencies for hook. As it empty, it means that hook is not dependent from any value of state or props. So count variable is consumed on first call of useEffect and than stays stale.

To correct this you should either completely remove second argument of useEffect or make array contain [count].

Callback is working as it receives previous count value as first argument.

So correct code will look like

function Tiker() {
    var [count, setCount] = useState(0);

    useEffect(() => {
        var timerID = setInterval(_=>
            setCount(count + 1)
        , 1000);

        return function cleanup() {
            clearInterval(timerID);
        };
    }, [count]);  // Put variable that useHook depends on

    return <div>
        this is ticker   
        <button onClick={() => 
            setCount(count + 1) //setCount(count+1) does work
        }>up </button>
        {count}
    </div>
}
Fyodor Yemelyanenko
  • 11,264
  • 1
  • 30
  • 38
  • the reason that i used an empty array is tell react to call the setinterval and clear interval just once in the mount/unmount cycle. (I followed this advice: https://stackoverflow.com/questions/53395147/use-react-hook-to-implement-a-self-increment-counter ) . when I add count to that array, the functions are called on every update of the component which occurs every second – yigal Jun 27 '19 at 23:15
  • 1
    This case you should use callback in `setCount` as you already did. – Fyodor Yemelyanenko Jun 27 '19 at 23:33
0

It appears that using a callback is the easiest way to change state when called from setInterval. as evident by Frodor comment above and from https://overreacted.io/making-setinterval-declarative-with-react-hooks/

yigal
  • 3,923
  • 8
  • 37
  • 59