5

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.

DangoSky
  • 185
  • 1
  • 2
  • 15

1 Answers1

8

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
Bhojendra Rauniyar
  • 83,432
  • 35
  • 168
  • 231
  • useCallback is used to memoize a function. There is no sense of using a callback in this case as its dependencies will change on every function rerender. You can refer to the previous state in setCount and make useCallback without dependencies – Eduard Mukans Nov 16 '19 at 11:00
  • @EduardMukans OP is learning about hooks and trying to understand how useCallback hook works. And that's what is explained here. – Bhojendra Rauniyar Nov 16 '19 at 11:02
  • @ Bhojendra Rauniyar I understand what you said, but I still have a problem. After I click the button for the first time, the value of count starts to be cached, so the count remains the same when I click for the second time. But why does the second click still print size:2? – DangoSky Nov 16 '19 at 11:12
  • Since count state remains same. Demo function will not re-render and the set.add(count) will not be called. For your query, why second click still print size: 2 is because it returns for the first time after it's being cached. After that it will not be called. – Bhojendra Rauniyar Nov 16 '19 at 14:05
  • @Bhojendra Rauniyar Yes, Demo function will not re-render because since count state remains same. So why does it still print size: 2 on the second click? I don't understand much that 'it returns for the first time after it's being cached'. Can trouble you to explain it? – DangoSky Nov 16 '19 at 14:43
  • I mean for the first time (with cached value), the useCallback will reflect the component and after that it won't. Hope, it's clear now. – Bhojendra Rauniyar Nov 16 '19 at 17:56