javascriptreact-hooksclosures

How does React's useCallback read the variable in closures


In the following example, when the button is clicked, cb2 uses the memoized function. However, why doesn’t cb2 use the first render closures (countVal: 0)?

function TestHook() {

  const [count, setCount] = useState(0)
  let countVal = 0

  const cb = useCallback(() => {
    console.log('cb dep [count]', count, countVal)
  }, [count])

  const cb2 = useCallback(() => {
    console.log('cb2 dep []', count, countVal)
  }, [])

  useEffect(() => {
    cb() // first time: 0, 0 second time: 1, 0 
    cb2() // first time: 0, 0 second time: 0, 1
  })

  const add = () => {
    console.log('add', count)
    countVal ++
    setCount(count + 1)
  }

  return (
    <>
      <button onClick={add}>add</button>
    </>
  );
}

Question:
Can anyone explain the result of cb2() after a re-render?


Solution

  • First render:

    Second render (after clicking the button):

    Why does cb2() log 0, 1 on the second render?

    useCallback([]): Since cb2 has no dependencies ([]), it doesn't recreate the callback after the first render. This means that on the second render, cb2 is still using the closure from the first render, but React captures the updated value of countVal due to how the JavaScript closure works.