Home > Mobile >  useState hook - passing in callback vs state value
useState hook - passing in callback vs state value

Time:06-05

In this very simple click counter example:

const App = () => {
  const [count, setCount] = useState(0)

  const handleClick = () => {
    setCount(count   1)
  }
  return <button onClick={handleClick}>{count}</button>
}

My understanding of the flow is:

  1. Component gets mounted with count=0
  2. When button is clicked, new count state is set with increment of 1.
  3. That triggers a re-render, so now I have count=1, and repeats.

However, using the same understanding, why does it not work here?

const App = () => {
  const [count, setCount] = useState(0)

  useEffect(() => {
    const timer = setInterval(() => {
      setCount(count   1)
    }, 500)

    return () => {
      clearInterval(timer)
    }
  }, [])

  console.log(count) // this stops at 1, so the timer stops triggering??
  return <h1>{count}</h1> // get stuck at 1
}

The outcome of the above code is that the count gets stuck at value 1. (and weirdly the console.log stops too).

I thought each time the setInterval timer triggers, the count increment will cause a re-render with new count value and therefore it will just increase by 1 forever?

The fix here is to simply pass in a function argument to access prevState:

const timer = setInterval(() => {
      setCount(oldCount => oldCount   1)
    }, 500)

But why couldn't the first approach work? Hope someone can point me to a good article or documentation of this, I tried searching around but couldn't get any explanation.

CodePudding user response:

The first approach doesn't work because with this

setCount(count   1)

You are creating a copy of the count value at that particular time you created the callback. This means that every 500ms you will re-execute this line

setCount(0   1)

That won't cause a re-render because react is intelligent enough to understand that you are passing the same value to the setCount function so the re-render would not be necessary.

However by passing a callback to setCount:

setCount(oldCount => oldCount   1)

You are saying that you want the current value of the state count, so each time the argument of that function will be different and therefore it will cause a re-render.

You can find a doc about this topic here: https://reactjs.org/docs/hooks-reference.html#functional-updates

  • Related