Busque em todas as páginas da documentação
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
// ANTES: Cada pressionamento de tecla re-renderiza toda a lista de produtos (47 renderizações)
function ProductPage() {
const [search, setSearch] = useState("");
const [products] = useState<Product[]>(initialProducts);
return (
<div>
<input value={search} onChange={(e) => setSearch(e.target.value)} />
<ProductList products={products} /> {/* Re-renderiza a cada pressionamento de tecla */}
<Footer /> {/* Também re-renderiza desnecessariamente */}
</div>
);
}
// DEPOIS: Apenas SearchBar re-renderiza ao digitar (3 renderizações)
function ProductPage() {
const [products] = useState<Product[]>(initialProducts);
return (
<div>
<SearchBar /> {/* Estado colocado aqui */}
<ProductList products={products} /> {/* Não re-renderiza mais */}
<Footer /> {/* Não re-renderiza mais */}
</div>
);
}
function SearchBar() {
const [search, setSearch] = useState("");
return <input value={search} onChange={(e) => setSearch(e.target.value)} />;
}Quando usar isso: Quando o Profiler do React DevTools destacar componentes piscando a cada interação, ou quando a UI parecer lenta durante a digitação, rolagem ou troca de abas. Sempre use o profiler primeiro para confirmar o problema antes de aplicar correções.
// ---- ANTES: Lista lenta com re-renderizações desnecessárias ----
interface Task {
id: string;
title: string;
completed: boolean;
}
// Problema: TaskItem re-renderiza para TODOS os itens quando qualquer estado muda
function TaskApp() {
const [tasks, setTasks] = useState<Task[]>(generateTasks(500));
const [filter, setFilter] = useState("all");
const [newTitle, setNewTitle] = useState("");
const toggleTask = (id: string) => {
setTasks((prev) =>
prev.map((t) => (t.id === id ? { ...t, completed: !t.completed } : t))
);
};
const filtered = tasks.filter((t) => {
if (filter === "done") return t.completed;
if (filter === "todo") return !t.completed;
return true;
});
return (
<div>
{/* Digitar aqui re-renderiza todos os 500 TaskItems */}
<input
value={newTitle}
onChange={(e) => setNewTitle(e.target.value)}
placeholder="Nova tarefa..."
/>
<select value={filter} onChange={(e) => setFilter(e.target.value)}>
<option value="all">Todas</option>
<option value="done">Concluídas</option>
<option value="todo">A Fazer</option>
</select>
<ul>
{filtered.map((task) => (
<TaskItem
key={task.id}
task={task}
onToggle={() => toggleTask(task.id)} // Nova função a cada renderização!
/>
))}
</ul>
</div>
);
}
function TaskItem({ task, onToggle }: { task: Task; onToggle: () => void }) {
// Simula renderização custosa
const start = performance.now();
while (performance.now() - start < 1) {} // 1ms por item = 500ms total
return (
<li onClick={onToggle} style={{ textDecoration: task.completed ? "line-through" : "none" }}>
{task.title}
</li>
);
}
// ---- DEPOIS: Otimizado - reduz re-renderizações de 500 para ~1-3 por interação ----
function TaskAppOptimized() {
const [tasks, setTasks] = useState<Task[]>(generateTasks(500));
const [filter, setFilter] = useState("all");
const toggleTask = useCallback((id: string) => {
setTasks((prev) =>
prev.map((t) => (t.id === id ? { ...t, completed: !t.completed } : t))
);
}, []);
const filtered = useMemo(
() =>
tasks.filter((t) => {
if (filter === "done") return t.completed;
if (filter === "todo") return !t.completed;
return true;
}),
[tasks, filter]
);
return (
<div>
{/* Estado colocado - digitar não re-renderiza mais a lista */}
<NewTaskInput
onAdd={(title) =>
setTasks((prev) => [...prev, { id: crypto.randomUUID(), title, completed: false }])
}
/>
<select value={filter} onChange={(e) => setFilter(e.target.value)}>
<option value="all">Todas</option>
<option value="done">Concluídas</option>
<option value="todo">A Fazer</option>
</select>
<ul>
{filtered.map((task) => (
<MemoizedTaskItem key={task.id} task={task} onToggle={toggleTask} />
))}
</ul>
</div>
);
}
// Entrada de nova tarefa com estado colocado - isolado da lista
function NewTaskInput({ onAdd }: { onAdd: (title: string) => void }) {
const [title, setTitle] = useState("");
return (
<input
value={title}
onChange={(e) => setTitle(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter" && title.trim()) {
onAdd(title.trim());
setTitle("");
}
}}
placeholder="Nova tarefa..."
/>
);
}
// Item memoizado - re-renderiza apenas quando sua própria tarefa muda
const MemoizedTaskItem = memo(function TaskItem({
task,
onToggle,
}: {
task: Task;
onToggle: (id: string) => void;
}) {
const start = performance.now();
while (performance.now() - start < 1) {}
return (
<li
onClick={() => onToggle(task.id)}
style={{ textDecoration: task.completed ? "line-through" : "none" }}
>
{task.title}
</li>
);
});O que isso demonstra:
NewTaskInput impede que 500 itens re-renderizem a cada pressionamento de tecla.React.memo em MemoizedTaskItem pula re-renderizações quando as props task e onToggle não mudam.useCallback em toggleTask estabiliza a referência da função para que o memo funcione.useMemo em filtered evita recalcular o filtro em mudanças de estado não relacionadas.setState é chamado, o React re-renderiza esse componente e todos os seus descendentes. Este é o gatilho de re-renderização mais comum.useContext(SomeContext) re-renderiza sempre que o valor do contexto muda, independentemente de o campo específico que ele usa ter mudado.onClick={() => handleClick(id)} cria uma nova função a cada renderização. Se o filho estiver envolvido em memo, essa nova referência derrota a otimização.style={{ color: "red" }} ou data={{ items }} criam uma nova referência de objeto a cada renderização, quebrando a comparação rasa em memo.Padrão children para evitar re-renderizações:
// ANTES: SlowChild re-renderiza quando count muda
function Parent() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount((c) => c + 1)}>{count}</button>
<SlowChild />
</div>
);
}
// DEPOIS: SlowChild passado como children, não re-renderizará
function CounterWrapper({ children }: { children: React.ReactNode }) {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount((c) => c + 1)}>{count}</button>
{children}
</div>
);
}
function Parent() {
return (
<CounterWrapper>
<SlowChild /> {/* Criado no escopo do Parent, não do CounterWrapper */}
</CounterWrapper>
);
}Dividindo componentes para isolar estado:
// ANTES: Estado de hover re-renderiza o card inteiro
function ProductCard({ product }: { product: Product }) {
const [isHovered, setIsHovered] = useState(false);
return (
<div
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<ExpensiveChart data={product.data} />
<HoverOverlay visible={isHovered} />
</div>
);
}
// DEPOIS: Estado de hover isolado em seu próprio componente
function ProductCard({ product }: { product: Product }) {
return (
<div>
<ExpensiveChart data={product.data} />
<HoverArea />
</div>
);
}
function HoverArea() {
const [isHovered, setIsHovered] = useState(false);
return (
<div
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<HoverOverlay visible={isHovered} />
</div>
);
}React.memo preserva os tipos de props. Para componentes genéricos, faça um cast após envolver: memo(MyComponent) as typeof MyComponent.useCallback é verificado por tipo pelo TypeScript e pela regra ESLint react-hooks/exhaustive-deps.children, tipifique children como React.ReactNode para máxima flexibilidade.Adicionar memo em tudo sem profilizar - memo tem sobrecarga (comparação rasa a cada renderização). Para componentes simples que renderizam rapidamente, o custo da comparação excede o custo da renderização. Correção: Profile primeiro. Envolva apenas componentes que o Profiler mostra serem custosos e re-renderizarem desnecessariamente.
Chaves instáveis causando remontagens - Usar o índice do array como key ou gerar chaves com Math.random() força o React a desmontar e remontar componentes, o que é mais custoso do que uma re-renderização. Correção: Use IDs estáveis e únicos de seus dados.
Esquecer useCallback ao passar manipuladores para filhos memoizados - memo no filho é inútil se o pai passa uma nova referência de função a cada renderização. Correção: Envolva as funções manipuladoras em useCallback ou reestruture para que o filho possua o manipulador.
Contexto causando re-renderizações globais - Um único contexto com estado e dispatch causa a re-renderização de todos os consumidores em qualquer mudança de estado. Correção: Divida em StateContext e DispatchContext separados, ou use Zustand com seletores.
Espalhar props derrotando memo - <Child {...props} /> onde props é reconstruído a cada renderização cria novas referências de objeto, fazendo com que memo sempre veja props "alteradas". Correção: Passe props individuais ou memoize o objeto de props.
| Abordagem | Trade-off |
|---|---|
| Colocação de estado | Correção mais simples; pode exigir reestruturação da árvore de componentes |
React.memo + useCallback | Controle preciso; adiciona boilerplate |
Padrão children | Overhead zero; funciona apenas quando o componente que detém o estado envolve o conteúdo |
| React Compiler | Automático; experimental, ainda não estável |
| Seletores Zustand | Substitui contexto; adiciona dependência |
| Virtualização | Renderiza apenas itens visíveis; altera o comportamento de rolagem |
useDeferredValue | Mantém a entrada responsiva; mostra conteúdo desatualizado brevemente |
Um componente pai re-renderizando causa a re-renderização de todos os seus filhos, mesmo que suas props não tenham mudado. Este é o comportamento padrão do React e a principal fonte de renderizações desperdiçadas.
Ao mover o estado para o componente que realmente o utiliza, os componentes pais não re-renderizam mais quando esse estado muda. Por exemplo, mover o estado de entrada de pesquisa para um componente SearchBar impede que o componente irmão ProductList re-renderize a cada pressionamento de tecla.
Funções inline como onClick={() => handleClick(id)} criam uma nova referência de função a cada renderização. memo usa comparação rasa, então ele vê uma nova prop e re-renderiza o filho de qualquer maneira.
Correção: Envolva o manipulador em useCallback ou reestruture para que o filho possua o manipulador.
function CounterWrapper({ children }: { children: React.ReactNode }) {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount((c) => c + 1)}>{count}</button>
{children}
</div>
);
}Os filhos são criados no escopo do pai, não dentro de CounterWrapper, então eles não re-renderizam quando count muda. Use-o quando um componente que detém o estado envolve conteúdo que ele não precisa controlar.
Sim. Usar o índice do array como key ou gerar chaves com Math.random() força o React a desmontar e remontar componentes em vez de atualizá-los. Remontar é mais custoso do que re-renderizar. Sempre use IDs estáveis e únicos de seus dados.
Qualquer componente que chama useContext(SomeContext) re-renderiza sempre que o valor do contexto muda, independentemente de qual campo mudou. Se estado e dispatch compartilham um contexto, cada mudança de estado re-renderiza todos os consumidores, incluindo aqueles que apenas despacham.
Correção: Divida em StateContext e DispatchContext, ou use Zustand com seletores.
Sim. <Child {...props} /> onde props é reconstruído a cada renderização cria novas referências de objeto a cada vez, fazendo com que memo sempre veja props "alteradas". Passe props individuais ou memoize o objeto de props em vez disso.
function Wrapper({ children }: { children: React.ReactNode }) {
return <div>{children}</div>;
}Use React.ReactNode para máxima flexibilidade. Ele aceita elementos, strings, números, fragments, portals e null.
React.memo preserva os tipos de props, mas para componentes genéricos, você precisa de uma asserção de tipo após envolver:
const MemoizedList = memo(MyList) as typeof MyList;Sem a asserção, o parâmetro de tipo genérico é perdido.
Sempre profile primeiro para confirmar que o componente é caro e re-renderiza desnecessariamente.
useDeferredValue ainda re-renderiza o componente lento, mas com prioridade menor, mantendo a entrada responsiva enquanto mostra conteúdo desatualizado brevementeA colocação de estado é mais simples quando possível; useDeferredValue é útil quando o componente lento realmente depende do estado em mudança.
Revisado por Chris St. John·Última atualização: 16 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥