Menu

Hook useCallback no React: quando ele realmente ajuda

useCallback retorna a mesma função entre renderizações até as dependências dela mudarem. Ele só ajuda quando essa função é passada para um filho com memo ou usada como dependência de um efeito. Aprenda a sintaxe, a diferença para o useMemo e os erros a evitar.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

useCallback guarda em cache uma função entre renderizações: ele retorna o mesmo objeto de função até que uma das dependências mude. Isso só importa quando algo compara a função por referência, o que na prática significa um filho envolvido em memo ou um efeito que lista a função como dependência.

Os dois botões são memoizados e fazem a mesma coisa. Alterne o tema e observe o Console: só aparece render Inline function, porque saveInline é uma função nova a cada renderização, então o memo vê uma prop alterada. saveCached é a mesma função toda vez, então esse botão é pulado.

O que ele guarda em cache

const cachedFn = useCallback(fn, dependencies);
  • fn é a função que você quer manter. O React não a chama; ele a devolve para você.
  • dependencies lista todos os valores do componente que fn lê. A cada renderização, o React os compara com os anteriores usando Object.is.
  • Se nada mudou, você recebe a função da renderização anterior. Se algo mudou, você recebe a função que passou desta vez, e o React guarda essa para a próxima.

Repare no que isso significa: a arrow function dentro do useCallback continua sendo criada a cada renderização, porque ela é só um argumento. O useCallback não economiza esse trabalho. A única função dele é devolver uma função mais antiga e idêntica para que a referência continue a mesma.

useCallback vs useMemo

São o mesmo mecanismo. O useMemo guarda em cache o resultado de chamar uma função, o useCallback guarda a própria função:

// These two lines are equivalent
const handleSave = useCallback(() => save(draft), [draft]);
const handleSave = useMemo(() => () => save(draft), [draft]);

Use useCallback para funções, porque se lê melhor, e useMemo para valores calculados, como uma lista filtrada ou um objeto de opções.

Eliminando dependências com funções de atualização

Uma função em cache precisa listar todos os valores de estado que lê, e sempre que esse estado muda, a função muda também. Para uma lista de linhas memoizadas, isso é um problema: uma função remove que lê todos muda sempre que todos muda, então todas as linhas restantes renderizam de novo depois de cada remoção. Use a forma do setter com função de atualização para que a função nem leia mais todos.

Remova uma linha: o Console fica quieto, porque as outras linhas receberam o mesmo objeto todo e a mesma função remove de antes. Troque a função por useCallback((id) => setTodos(todos.filter((t) => t.id !== id)), [todos]) e tente de novo: agora todas as linhas restantes registram uma renderização depois de cada remoção.

Funções como dependências de efeitos

Um efeito que chama uma função declarada no componente precisa listar essa função como dependência. Se a função é nova a cada renderização, o efeito executa depois de toda renderização. O useCallback a mantém estável até as entradas dela mudarem:

Digitar uma mensagem não reconecta. Trocar de sala registra um disconnect da sala antiga e um connect na nova. Apague o wrapper useCallback (mantenha a arrow function) e cada tecla desconecta e reconecta.

Mas aqui existe uma solução mais simples: mova a função para dentro do efeito. Assim o efeito depende diretamente de roomId e nenhuma função precisa ser estável.

useEffect(() => {
    function createOptions() {
        return { serverUrl: 'wss://chat.example', roomId };
    }
    const options = createOptions();
    // ...
}, [roomId]);

Prefira isso quando a função só é usada pelo efeito. Mantenha o useCallback para funções que você também passa para baixo ou retorna de um hook personalizado, em que quem chama pode colocá-las nos próprios arrays de dependências.

Conferindo se uma função mudou

Quando um filho com memo continua renderizando e você não sabe por quê, confira se a prop de função é realmente o mesmo objeto entre renderizações. Guarde a anterior em uma ref e compare:

Clique em Count: o Console diz increment changed: false, porque step não mudou. Clique em Step e ele diz true: step é uma dependência, então o React devolveu a função nova. Uma função que muda a cada renderização normalmente significa que uma dependência é ela mesma nova a cada renderização, como um objeto ou outra função declarada no componente.

useCallback em hooks personalizados

Um hook personalizado que retorna funções é um bom lugar para o useCallback, mesmo quando você não sabe quem vai chamá-lo. O componente que usa o hook pode passar essas funções para um filho com memo ou listá-las em um efeito, e não consegue torná-las estáveis de fora. Envolvê-las dentro do hook dá a todo chamador uma referência estável por padrão:

function useCounter(initial = 0) {
    const [count, setCount] = useState(initial);
    const increment = useCallback(() => setCount((c) => c + 1), []);
    const reset = useCallback(() => setCount(initial), [initial]);
    return { count, increment, reset };
}

O erro comum: envolver tudo

Um useCallback em volta de um handler que vai direto para um <button> ou <input> não faz nada útil. O React não pula trabalho nenhum em um elemento do DOM porque o handler continuou o mesmo, e um componente que não está envolvido em memo renderiza de qualquer jeito quando o pai renderiza.

// Pointless: <button> does not care that the function is the same
const handleClick = useCallback(() => setOpen(true), []);
return <button onClick={handleClick}>Open</button>;

Cada useCallback extra adiciona um array de dependências que alguém pode errar, e uma dependência desatualizada é um bug de verdade (a função continua lendo estado antigo). Adicione-o quando houver um filho com memo ou um array de dependências do lado que recebe, não por padrão.

O React Compiler 1.0, quando o seu build o usa, memoiza funções e valores automaticamente, o que elimina a necessidade da maioria das chamadas de useCallback escritas à mão. Sem o compilador, as regras acima continuam valendo, e o useCallback manual ainda é comum em código existente.

Perguntas frequentes

O que o useCallback faz?

Ele retorna o mesmo objeto de função em toda renderização até que uma das dependências mude. Ele não impede a função de ser criada; ele devolve a função de uma renderização anterior em vez da nova.

Quando devo usar o useCallback?

Quando você passa uma função para um filho envolvido em memo, ou quando a função é dependência de um efeito ou de outro hook. Nos dois casos, uma função nova a cada renderização anularia a otimização ou executaria o efeito de novo.

Qual é a diferença entre useCallback e useMemo?

useCallback(fn, deps) guarda em cache o próprio fn. useMemo(() => value, deps) guarda em cache o valor que uma função retorna. useCallback(fn, deps) é exatamente useMemo(() => fn, deps).

Devo envolver toda função em useCallback?

Não. Se a função vai para um elemento DOM comum ou para um componente que não é memoizado, uma referência estável não muda nada, e os arrays de dependências extras deixam o código mais difícil de ler e mais fácil de errar.

O useCallback deixa meu componente mais rápido?

Não sozinho. Ele só ajuda quando algo mais adiante compara a função por referência, como o memo ou o array de dependências de um efeito.

Ilustração das linguagens de programação do Coddy

Aprenda a programar com o Coddy

COMEÇAR