Search code examples
javascriptreactjsreact-hooks

How does React useState and useCallback hook work when useCallback lacks dependencies


I am studying react-hook recently. I have encountered a problem which makes me unable to understand in the process of practice.

import React, { useState, useCallback } from 'react';
const set = new Set();

function Demo() {
  const [count, setCount] = useState(0);

  const changeValue = useCallback(() => {
    setCount(count + 1);
  }, []); 

  set.add(count);
  console.log('size: ', set.size);

  return(
    <div>
      <p>Hello React Hook</p>
      <p>{count}</p>
      <button onClick={changeValue}>count++</button>
    </div>
  )
}

export default Demo;

// If you click the button multiple times, the output is:
// size: 1
// size: 2
// size: 2

I wrote a timer using react-hook. As I expected, the count value shown is always 1, because I didn't use count as a dependency of useCallback.

But what I can't understand is console.log('size: ', set.size) only printed three times, why? Every time I click the count++ button, it will cause the Demo function to re-execute. So every time I click the button, shouldn't console.log('size: ', set.size) will be executed? But in fact it only executed three times.

And why does size keep 2 unchanged? I understand that setCount will replace a new count every time, so size should not increase?

Please help me answer my doubts, thank you very much.

You can test my code here.


Solution

  • The Demo component will re-render whenever its state is changed. So, it will have the size 1 as you have added count state to the set:

    set.add(count); // 0 in initial render, size is 1
    

    Now, when you click on the button, it will use the callback useCallback that is memoized by react. It means it will first check if has cached value or not. So, at the first click it has no cached value. And thus it calls its callback to set (update) the count state. Now, count is 1 and you also added the count in to the set.

    set.add(count); // 1 in first click, size is 2
    

    On following clicks useCallaback has cached the count value but you have not set the count in second parameter of useCallback hook and thus it will just return the cached value that is 1 - the count state. And it will return always from this. Again I repeat, this time the set size is 2.

    You should watch for the changes when put the count state in second argument:

      const changeValue = useCallback(() => {
        setCount(count + 1);
      }, [count]); // we watch it on every click
    

    Now, you will see the changing value each time you click on the button.


    Also note: Don't confuse the changeValue is not being called on following clicks. This calls the useCallback every time but it just doesn't update the state:

      const changeValue = useCallback(() => {
        console.log('clicked') // logged every time on button click
        setCount(count + 1); // count state is not being watched, 
         // setCount won't update the count on following clicks
         // once it caches the state.
      },[]); // need to watch state here to update the state