React Hooks useCallback faz com que o filho volte a renderizar

Estou tentando mudar de componentes de classe para componentes funcionais usando os novos ganchos. No entanto, parece que comuseCallbackeceberei renderizações desnecessárias de crianças, diferentemente das funções de classe nos componentes de class

Abaixo tenho dois trechos relativamente simples. O primeiro é o meu exemplo escrito como classes e o segundo é o meu exemplo reescrito como componentes funcionais. O objetivo é obter o mesmo comportamento com componentes funcionais e com componentes de class

aso de teste do componente @Class

class Block extends React.PureComponent {
  render() {
    console.log("Rendering block: ", this.props.color);

    return (
        <div onClick={this.props.onBlockClick}
          style = {
            {
              width: '200px',
              height: '100px',
              marginTop: '12px',
              backgroundColor: this.props.color,
              textAlign: 'center'
            }
          }>
          {this.props.text}
         </div>
    );
  }
};

class Example extends React.Component {
  state = {
    count: 0
  }
  
  
  onClick = () => {
    console.log("I've been clicked when count was: ", this.state.count);
  }
  
  updateCount = () => {
    this.setState({ count: this.state.count + 1});
  };
  
  render() {
    console.log("Rendering Example. Count: ", this.state.count);
    
    return (
      <div style={{ display: 'flex', 'flexDirection': 'row'}}>
        <Block onBlockClick={this.onClick} text={'Click me to log the count!'} color={'orange'}/>
        <Block onBlockClick={this.updateCount} text={'Click me to add to the count'} color={'red'}/>
      </div>
    );
  }
};

ReactDOM.render(<Example/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>

<div id='root' style='width: 100%; height: 100%'>
</div>

Caso de teste do componente funcional

const Block = React.memo((props) => {
  console.log("Rendering block: ", props.color);
  
  return (
      <div onClick={props.onBlockClick}
        style = {
          {
            width: '200px',
            height: '100px',
            marginTop: '12px',
            backgroundColor: props.color,
            textAlign: 'center'
          }
        }>
        {props.text}
       </div>
  );
});

const Example = () => {
  const [ count, setCount ] = React.useState(0);
  console.log("Rendering Example. Count: ", count);
  
  const onClickWithout = React.useCallback(() => {
    console.log("I've been clicked when count was: ", count);
  }, []);
  
  const onClickWith = React.useCallback(() => {
    console.log("I've been clicked when count was: ", count);
  }, [ count ]);
  
  const updateCount = React.useCallback(() => {
    setCount(count + 1);
  }, [ count ]);
  
  return (
    <div style={{ display: 'flex', 'flexDirection': 'row'}}>
      <Block onBlockClick={onClickWithout} text={'Click me to log with empty array as input'} color={'orange'}/>
      <Block onBlockClick={onClickWith} text={'Click me to log with count as input'} color={'cyan'}/>
      <Block onBlockClick={updateCount} text={'Click me to add to the count'} color={'red'}/>
    </div>
  );
};

ReactDOM.render(<Example/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>

<div id='root' style='width: 100%; height: 100%'>
</div>

No primeiro (componentes da classe), eu posso atualizar a contagem via bloco vermelho sem renderizar novamente nenhum dos blocos, e posso consolar livremente o registro atual através do bloco laranja.

No segundo (componentes funcionais), a atualização da contagem via bloco vermelho acionará uma nova renderização do bloco vermelho e ciano. Isso ocorre porque ouseCallback criará uma nova instância de sua função porque a contagem mudou, fazendo com que os blocos obtenham um novoonClick prop e, portanto, renderize novamente. O bloco laranja não será renderizado novamente porque ouseCallback usado para a laranjaonClick não depende do valor da contagem. Isso seria bom, mas o bloco laranja não mostrará o valor real da contagem quando você clicar nele.

Eu pensei que o ponto de teruseCallback foi para que os filhos não recebessem novas instâncias da mesma função e não tivessem repetições desnecessárias, mas isso parece acontecer de qualquer maneira no segundo em que a função de retorno de chamada usa uma única variável que acontece com frequência, se não sempre, da minha experiência .

Então, como eu iria fazer issoonClick função dentro de um componente funcional sem que os filhos sejam renderizados novamente? Isso é possível?

Update (solução): Usando a resposta de Ryan Cogswell abaixo, criei um gancho personalizado para facilitar a criação de funções de class

const useMemoizedCallback = (callback, inputs = []) => {
    // Instance var to hold the actual callback.
    const callbackRef = React.useRef(callback);

    // The memoized callback that won't change and calls the changed callbackRef.
    const memoizedCallback = React.useCallback((...args) => {
      return callbackRef.current(...args);
    }, []);

    // The callback that is constantly updated according to the inputs.
    const updatedCallback = React.useCallback(callback, inputs);

    // The effect updates the callbackRef depending on the inputs.
    React.useEffect(() => {
        callbackRef.current = updatedCallback;
    }, inputs);

    // Return the memoized callback.
    return memoizedCallback;
};

Eu posso usar isso no componente function com muita facilidade e simplesmente passar o onClick para o filho. Ele não renderiza mais a criança, mas ainda faz uso de vars atualizado

const onClick = useMemoizedCallback(() => {
    console.log("NEW I've been clicked when count was: ", count);
}, [count]);

questionAnswers(1)

yourAnswerToTheQuestion