Hook useCallback
Cache a function definition between renders to maintain a stable reference.
Busque em todas as páginas da documentação
Cache a function definition between renders to maintain a stable reference.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Cartão de receita de referência rápida - pronto para copiar e colar.
const handleClick = useCallback(() => {
doSomething(a, b);
}, [a, b]);
// Padrão comum: estabilizar um callback passado para um filho memoizado
const handleDelete = useCallback((id: string) => {
setItems((prev) => prev.filter((item) => item.id !== id));
}, []);Quando usar isso: Você passa uma função como prop para um filho React.memo, ou como dependência em um useEffect ou useMemo, e precisa que ela não mude a cada renderização.
"use client";
import { memo, useCallback, useState } from "react";
interface TodoItemProps {
id: number;
text: string;
onDelete: (id: number) => void;
}
const TodoItem = memo(function TodoItem({ id, text, onDelete }: TodoItemProps) {
console.log(`Rendering: ${text}`);
return (
<li className="flex items-center justify-between py-1">
<span>{text}</span>
<button onClick={() => onDelete(id)} className="text-red-500 text-sm">
Delete
</button>
</li>
);
});
export function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: "Learn React" },
{ id: 2, text: "Build an app" },
{ id: 3, text: "Ship it" },
]);
const [input, setInput] = useState("");
const handleDelete = useCallback((id: number) => {
setTodos((prev) => prev.filter((todo) => todo.id !== id));
}, []);
const handleAdd = useCallback(() => {
if (!input.trim()) return;
setTodos((prev) => [...prev, { id: Date.now(), text: input }]);
setInput("");
}, [input]);
return (
<div className="space-y-2">
<div className="flex gap-2">
<input
value={input}
onChange={(e) => setInput(e.target.value)}
className="border rounded px-2 py-1 flex-1"
placeholder="New todo"
/>
<button onClick={handleAdd} className="px-3 py-1 border rounded">
Add
</button>
</div>
<ul>
{todos.map((todo) => (
<TodoItem key={todo.id} id={todo.id} text={todo.text} onDelete={handleDelete} />
))}
</ul>
</div>
);
}O que isso demonstra:
handleDelete está envolvido em useCallback com dependências [], então sua referência nunca muda.TodoItem está envolvido em React.memo, então ele só re-renderiza quando suas props mudam.useCallback, digitar no input faria com que cada TodoItem re-renderizasse porque handleDelete seria uma nova função a cada vez.handleAdd depende de input, então ele atualiza quando o input muda - isso está correto.useCallback(fn, deps) é equivalente a useMemo(() => fn, deps)Object.is.| Parâmetro | Tipo | Descrição |
|---|---|---|
fn | (...args: A) => R | A função a ser memoizada |
dependencies | unknown[] | Array de valores reativos usados dentro da função |
| Retorno | Tipo | Descrição |
|---|---|---|
memoizedFn | (...args: A) => R | Função em cache com uma referência estável |
Estabilizar um manipulador de eventos para useEffect:
const fetchData = useCallback(async () => {
const res = await fetch(`/api/items?page=${page}`);
setData(await res.json());
}, [page]);
useEffect(() => {
fetchData();
}, [fetchData]);Com genéricos:
const handleSelect = useCallback(<T extends { id: string }>(item: T) => {
setSelectedId(item.id);
}, []);Callback estável sem dependências (comum para padrões de atualizador):
const toggle = useCallback(() => {
setOpen((prev) => !prev);
}, []);Um exemplo completo e executável que demonstra claramente como React.memo funciona em conjunto com useCallback.
"use client";
import React, { useState, useCallback } from "react";
// 1. Componente Filho Memoizado
const MemoizedChild = React.memo(function MemoizedChild({
onClick,
label,
}: {
onClick: () => void;
label: string;
}) {
console.log(`${label} rendered`); // ← Observe o console
return (
<button onClick={onClick} className="border rounded px-3 py-1">
{label}
</button>
);
});
// 2. Filho Regular (não memoizado) para comparação
function RegularChild({
onClick,
label,
}: {
onClick: () => void;
label: string;
}) {
console.log(`${label} (regular) rendered`);
return (
<button onClick={onClick} className="border rounded px-3 py-1">
{label} (regular)
</button>
);
}
export default function Parent() {
const [count, setCount] = useState(0);
const [otherState, setOtherState] = useState(0);
// Callback estável graças ao useCallback
const handleClick = useCallback(() => {
alert("Button clicked!");
}, []); // array de dependências vazio → nunca muda
return (
<div className="space-y-4 p-4">
<h2 className="text-lg font-bold">Parent Component</h2>
<p>Count: {count}</p>
<div className="flex gap-2">
<button
onClick={() => setCount((c) => c + 1)}
className="border rounded px-3 py-1"
>
Increment Count (causes parent re-render)
</button>
<button
onClick={() => setOtherState((s) => s + 1)}
className="border rounded px-3 py-1"
>
Update Other State ({otherState})
</button>
</div>
<div className="space-y-2">
<h3 className="font-semibold">Memoized Children</h3>
<div className="flex gap-2">
<MemoizedChild onClick={handleClick} label="Memoized Button 1" />
<MemoizedChild onClick={handleClick} label="Memoized Button 2" />
</div>
<h3 className="font-semibold">Regular (non-memoized) Children</h3>
<RegularChild onClick={handleClick} label="Regular Button" />
</div>
</div>
);
}O que acontece quando você clica em "Increment Count":
count mudou.MemoizedChild NÃO re-renderiza - React.memo faz uma comparação superficial das props. onClick é a mesma referência (graças ao useCallback) e label é uma string primitiva que não mudou. Você não verá o console.log.RegularChild RE-RENDERIZA - componentes funcionais normais sempre re-renderizam quando seu pai o faz, mesmo que a referência onClick seja estável.Sem useCallback (o que geralmente dá errado):
Se você remover useCallback e escrever const handleClick = () => { alert("Button clicked!"); }, então até mesmo MemoizedChild re-renderiza a cada renderização do pai porque uma nova função é criada a cada vez, fazendo com que a comparação de props falhe.
Principais conclusões:
useCallback torna a referência da função estável.React.memo verifica se as props são as mesmas (igualdade superficial) e pula a renderização se forem.React.memo faz apenas comparação superficial - se você passar objetos ou arrays, você também precisará de useMemo para eles.// TypeScript infere o tipo do callback a partir do uso
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
setQuery(e.target.value);
}, []);
// handleChange: (e: React.ChangeEvent<HTMLInputElement>) => void
// Ao passar para um filho, o tipo da prop do filho restringe o tipo do callback
interface Props {
onSelect: (id: string) => void;
}useCallback sem React.memo - Envolver um callback em useCallback não faz nada se o filho não for memoizado com React.memo. Correção: Use useCallback apenas quando o consumidor realmente se beneficia de uma referência estável.
Closure obsoleto (stale closure) - Se você omitir uma dependência, o callback fecha sobre um valor antigo da renderização em que foi criado. Correção: Inclua todos os valores reativos no array de dependências, ou use funções de atualizador (setState(prev => ...)) para evitar a dependência.
Excesso de memoização - Envolver toda função em useCallback adiciona sobrecarga cognitiva e uso de memória. Correção: Memoize apenas ao passar para filhos React.memo, ou quando usado como dependência de useEffect ou useMemo.
Dependências que mudam a cada renderização - Se uma dependência é um objeto ou array instável, a referência do callback muda a cada renderização de qualquer maneira. Correção: Memoize a dependência com useMemo ou reestruture para usar primitivos.
| Alternativa | Use Quando | Não Use Quando |
|---|---|---|
| Função inline | O filho não é memoizado, ou a função é usada apenas no mesmo componente | A função é passada para um filho React.memo |
useMemo | Você precisa memoizar um valor não-função | Você precisa memoizar uma função |
useReducer dispatch | Múltiplos filhos precisam acionar mudanças de estado - dispatch é sempre estável | Atualizações simples de valor único |
Callback de ref (useRef) | Você precisa de uma função estável que sempre leia os valores mais recentes | Você quer que a identidade da função mude quando as dependências mudam |
Regra geral: Comece sem useCallback. Adicione-o quando você perfilar e encontrar re-renderizações desnecessárias em filhos memoizados, ou quando uma função usada como dependência de efeito continuar re-disparando o efeito.
React.memo, o filho re-renderiza a cada renderização do pai, independentemente da estabilidade das props.useCallback apenas previne re-renderizações quando o consumidor verifica a igualdade de props (via React.memo ou arrays de dependência).useCallback e adicione-o quando a perfilagem mostrar re-renderizações desnecessárias.useCallback(fn, deps) é equivalente a useMemo(() => fn, deps).useCallback armazena em cache a própria função; useMemo armazena em cache o valor de retorno de uma função.useCallback para funções e useMemo para valores não-função.[], o React nunca detecta uma mudança de dependência, então ele retorna a mesma referência de função para sempre.setState(prev => ...)) que não dependem de valores externos.const fetchData = useCallback(async () => {
const res = await fetch(`/api/items?page=${page}`);
setData(await res.json());
}, [page]);
useEffect(() => {
fetchData();
}, [fetchData]); // Só re-executa quando a página mudadispatch de useReducer é sempre estável - ele nunca muda entre as renderizações.dispatch para múltiplos filhos sem preocupações com useCallback ou React.memo.const handleChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
setQuery(e.target.value);
},
[]
);
// Tipo: (e: React.ChangeEvent<HTMLInputElement>) => voidObject.is vê uma nova referência.useMemo ou reestruture para usar valores primitivos.React.memo ou ao usar como dependência de useEffect/useMemo.useCallback não vale a pena.const handleSelect = useCallback(
<T extends { id: string }>(item: T) => {
setSelectedId(item.id);
},
[]
);useCallback.Revisado por Chris St. John·Última atualização: 16 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥