2tuzi
2tuzi

Reputation: 66

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?

Upvotes: 1

Views: 74

Answers (2)

Yousaf
Yousaf

Reputation: 29334

why doesn’t cb2 use the first render closures (countVal: 0)?

It is using the countVal from the first render. Reason you see its value as 1 instead of zero is because clicking the "add" button increments the value of countVal variable.

After the first click, countVal from the first render is incremented to 1, so that is what gets logged from cb2() call on subsequent clicks.

Note on closures:

Functions close over variables, not their values. If you change the value of a variable (countVal in your example), any function that has a closure over that variable will see its latest value.

Upvotes: 2

Ayoub EL ABOUSSI
Ayoub EL ABOUSSI

Reputation: 704

First render:

  • count = 0
  • countVal = 0

Second render (after clicking the button):

  • setCount(count + 1) triggers a state update that causes a re-render.
  • countVal is incremented manually (but it's not part of React state, so React does not track it for re-renders).
  • cb2() logs 0, 1, where:
    • count is still 0 because cb2 is memoized with an empty dependency array, meaning it does not capture the updated count (but it's logged as 0 in cb2).
    • countVal is 1 because countVal was incremented during the previous render. Even though React doesn’t track countVal, it still holds the updated value within the closure of cb2 from the second render.

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.

Upvotes: 2

Related Questions