1

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?

0

2 Answers 2

2

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Yes, edited the answer.
"it doesn't recreate the callback after the first render." - small nitpick, but the callback is still recreate, it's just that react discards it/doesn't do anything with the new function, and instead useCallback() continues to return the original function reference.
2

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.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.