Como funcionam o hook useCallback e o React.memo?

Na última aula, você aprendeu sobre memoização e como o hook useMemo funciona. Nesta lição, você aprenderá como o hook useCallback e o React.memo funcionam. Na última aula, também mencionamos que useCallback serve para memorizar referências de função. Para React.memo, ele permite que você memorize um componente para evitar re-renderizações desnecessárias quando sua prop não mudou. Aqui está a sintaxe básica do hook useCallback:
const handleClick = useCallback(() => {
  // code goes here
}, [dependency]);
E aqui está a sintaxe básica de React.memo:
const MemoizedComponent = React.memo(({ prop }) => {
  return (
    <>
      {/* Presentation */}
    </>
  )
});
Vamos ver um exemplo do hook useCallback:
import { useState, useEffect } from "react";

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

  const handleClick = () => {
    setCount((prevCount) => prevCount + 1);
  };

  useEffect(() => {
    console.log("useEffect runs");
  }, [handleClick]);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleClick}>Increment</button>
    </div>
  );
}

export default Counter;
No componente, o efeito é executado sempre que handleClick muda porque a função handleClick está sendo recriada a cada renderização. Para corrigir isso, você precisa dizer ao React para tratar a função handleClick como a mesma coisa entre as renderizações memorizando-a com o hook useCallback, para que ela não seja recriada:
import { useState, useEffect, useCallback } from "react";

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

  // Memoize the handleClick function with useCallback
  const handleClick = useCallback(() => {
    setCount((prevCount) => prevCount + 1);
  }, []);

  useEffect(() => {
    console.log("useEffect runs");
  }, [handleClick]);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleClick}>Increment</button>
    </div>
  );
}

export default Counter;
Agora a função handleClick não está sendo recriada a cada renderização. Para mostrar como a função de ordem superior React.memo (ou memo) e o hook useCallback funcionam em conjunto, aqui está um componente Counter com uma função handleClick que precisa do useCallback mas atualmente não está usando:
import { useState, useEffect, useCallback } from "react";
import CounterChild from "./CounterChild";

function Counter() {
  const [count, setCount] = useState(0);
  const [timer, setTimer] = useState(new Date().toLocaleTimeString());

  const handleClick = () => {
    setCount(count + 1);
  };

  useEffect(() => {
    const interval = setInterval(() => {
      setTimer(new Date().toLocaleTimeString());
    }, 1000);

    return () => clearInterval(interval);
  }, []);

  return (
    <div>
      <h1>Time: {timer}</h1>
      <p>Count: {count}</p>
      <button onClick={handleClick}>Increment</button>
      <CounterChild onClick={handleClick} />
    </div>
  );
}

export default Counter;
Esta função também possui um timer no estado que é atualizado a cada segundo. Isso faz com que o componente seja re-renderizado toda vez que o timer mudar, fazendo com que a função handleClick seja recriada a cada renderização. É por isso que o handleClick precisa ser memorizado com useCallback. Aqui está o componente CounterChild:
const CounterChild = ({ onClick }) => {
  console.log("CounterChild component rendered");
  return <button onClick={onClick}>Increment from Child</button>;
};

export default CounterChild;
Este componente CounterChild recebe uma prop onClick, dando a você a capacidade de também incrementar o contador a partir dele. Como o componente CounterChild é um filho do componente Counter, ele também será renderizado sempre que o Counter re-renderizar devido à alteração do timer. Então, o CounterChild também precisa ser memoizado. Sem memoização, porque à medida que o componente é re-renderizado devido à atualização do timer a cada segundo, o componente CounterChild também é re-renderizado. Para evitar isso, você precisa memorizar o componente CounterChild com React.memo:
import React from "react";

const CounterChild = React.memo(({ onClick }) => {
  console.log("CounterChild component rendered");
  return <button onClick={onClick}>Increment from Child</button>;
});

export default CounterChild;
As coisas ainda não funcionam de forma otimizada mesmo após memorizar o CounterChild com React.memo. Isso acontece porque a função handleClick está sendo recriada a cada renderização, então ela também precisa ser memorizada com useCallback, para informar ao React que você precisa que a função permaneça a mesma entre as renderizações:
const handleClick = useCallback(() => {
  setCount((prevCount) => prevCount + 1);
}, [count]);
Agora, o componente só re-renderiza quando o estado count muda.
Este módulo não possui perguntas. Marque como concluído.