Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React hooks: new state value not reflecting in setInterval callback

I have a function react component that has a counter that starts from 10000 and goes to 0.

I am setting a setInterval callback using useEffect hook during component mounting. The callback then updates the counter state.

But I don't know why, the count value never decreases. Each time the callback runs count is 10000.

(I am using react & react-dom version 16.8.3)

Function component is as below:

import React, { useState, useEffect, useRef } from 'react'

const Counter = () => {
  const timerID = useRef()
  let [count, setCount] = useState(10000)

  useEffect(() => {
    timerID.current = setInterval(() => {
      //count here is always 10000
      if (count - 1 < 0) {
        setCount(0)
      } else {
        setCount(count - 1)
      }
    }, 1)
  }, [])

  return <h1 className="counter">{count}</h1>
}

export default Counter

Here is the link to codesandbox: link

like image 998
dedman Avatar asked Oct 29 '25 07:10

dedman


2 Answers

You need to watch for changes in count, and also clean up your useEffect():

useEffect(() => {
    timerID.current = setInterval(() => {
      if (count - 1 < 0) {
        setCount(0)
      } else {
        setCount(count - 1)
      }
    }, 100)

    return () => clearInterval(timerID.current);
  }, [count])

As @Pavel mentioned, Dan Abramov explains why here.

like image 150
Colin Ricardo Avatar answered Oct 31 '25 06:10

Colin Ricardo


There are 2 options:

1) Include count in the dependencies

This is not ideal, as it means a new setInterval will be created on every change of count, so you would need to clean it up on every render, example:

  useEffect(() => {
    timerID.current = setInterval(() => {
      //count here is always 10000
      if (count - 1 < 0) {
        setCount(0)
      } else {
        setCount(count - 1)
      }
    }, 1)
    return () => clearInterval(timerID.current) // Added this line
  }, [count]) // Added count here

2) Add the count in the setInterval callback function.

This is the best approach for intervals, as it avoids, setting new ones all the time.

 useEffect(() => {
    timerID.current = setInterval(() => {
      // count is used inside the setCount callback and has latest value
      setCount(count => {
        if (count - 1 < 0) { // Logic moved inside the function, so no dependencies
          if (timerID.current) clearInterval(timerID.current)
          return 0
        }
        return count - 1
      })
    }, 1)
    return () => {
      if (timerID.current) clearInterval(timerID.current) // Makes sure that the interval is cleared on change or unmount
    }
  }, [])

Here is the sandbox link

like image 24
denislexic Avatar answered Oct 31 '25 08:10

denislexic