
Junior Alves
Senior Developer
Foto: Unsplash
Atualizado: 26 de novembro de 2025 às 08:06
Leitura: 8 minutos de leitura
Criado: 23 de novembro de 2025
React setState é assíncrono?
Por isso você precisa entender os fundamentos!
Introdução
"Mas eu dei console.log logo depois do setState e o valor não mudou! Deve ser um bug do React!". Não, não é um bug. O que você encontrou não é uma falha na Matrix, mas sim um mal-entendido fundamental sobre como o React gerencia o estado e o ciclo de vida de renderização. É um ponto de virada, que separa os devs que apenas usam React daqueles que o entendem.
Neste post, vamos analisar e dissecar a explicação técnica definitiva que vai explicar o seu console.log e, finalmente, apresentar a solução sênior..
1. O Stale State em ação
Entender o timing da atualização de estado não é um mero capricho acadêmico; é uma necessidade estratégica em apps do mundo real. Imagine um dashboard interativo, onde cada clique em um filtro deve disparar uma busca de dados. A performance e a consistência dos dados são críticas. Uma chamada de API com um filtro desatualizado não só retorna dados errados, como também desperdiça recursos e frustra o usuário.
1.1 O cenário do problema
Nosso cenário é exatamente esse: um filtro de pesquisa em um dashboard. O objetivo é simples: assim que o usuário altera o valor do filtro, uma função deve ser chamada para buscar os novos dados correspondentes na API.
1.2 O código do Júnior
Um dev menos experiente, pensando de forma imperativa ("faça isso, depois faça aquilo"), produziria um código semelhante a este:
import { useState } from 'react';
// ...
const [filter, setFilter] = useState('');
function handleFilterChange(newFilterValue: string) {
// 1. O dev diz ao React para atualizar o estado.
setFilter(newFilterValue);
// 2. O erro fatal acontece aqui:
// O dev ACHA que está usando o novo estado imediatamente.
fetchData(filter); // 'filter' ainda tem o valor antigo desta renderização!
}
async function fetchData(currentFilter: string) {
// Lógica para chamar a API com o filtro...
console.log(`Buscando dados com o filtro: "${currentFilter}"`);
// Resultado no console: "Buscando dados com o filtro: """ (o valor inicial)
}1.3 A Análise do problema
O código acima falha porque a função fetchData é invocada com o valor antigo e obsoleto (stale) do estado filter. A razão para isso está no coração do modelo de funcionamento do React: dentro de um ciclo de renderização, o valor de uma variável de estado é um "snapshot" constante. A chamada setFilter(newFilterValue) não altera magicamente a constante filter na linha seguinte; ela apenas agenda uma nova renderização, que terá um novo snapshot do estado. A falha é um conflito direto entre a expectativa imperativa do dev ("faça isso, depois faça aquilo") e a natureza declarativa e reativa do React ("descreva como a UI deve ser para este estado").
Agora que vimos o problema, vamos entender por que o React não te obedeceu.
2. A explicação técnica definitiva
A discussão aqui não é sobre "síncrono vs. assíncrono" no sentido tradicional de Promises e async/await. É sobre o modelo declarativo de estado e renderização do React, que é a base de sua eficiência e previsibilidade.
2.1 O estado é um retrato (snapshot), não uma variável mutável
Para entender o comportamento do setState, você precisa internalizar dois conceitos: Render Cycle (Ciclo de Renderização) e Closure.
- Closure e Render Cycle: Quando o React renderiza seu componente, a função
handleFilterChange"fecha" (via closure) sobre as variáveis e o estado daquele momento específico. A closure é, essencialmente, um snapshot de todos os dados 'externos' congelados no tempo. Dentro da execução dehandleFilterChange, a variável filter se comporta como uma constante (const) que guarda o valor do estado no momento em que a função foi criada. Esse mesmo mecanismo de closure é o motivo pelo qual devs às vezes encontram 'stale state' dentro de hooks comouseCallbackcom um array de dependências vazio, a função permanece congelada com o estado inicial da primeira renderização. - Stale State: A chamada
setFilternão altera essa "constante". Ela sinaliza ao React: "Ei, termine o que está fazendo. Quando puder, por favor, renderize este componente novamente, mas da próxima vez, use este novo valor para o estado filter." O valor de filter que você tenta usar imediatamente após é, portanto, um Stale State, um retrato do passado.
2.2 O Garçom Eficiente: Entendendo o Batching de Atualizações
Para otimizar a performance, o React implementa um mecanismo chamado Batching (agrupamento).
Imagine um garçom em um restaurante lotado. Em vez de correr para a cozinha a cada item que um cliente pede, ele anota o pedido da mesa inteira e leva tudo de uma vez. O React faz o mesmo. Ele agrupa múltiplas chamadas setState que ocorrem em um mesmo evento (como um clique) em uma única re-renderização. Isso evita renderizações intermediárias e desnecessárias, que seriam um desastre para a performance.
Portanto, para resolver a confusão do título de uma vez por todas: A chamada setState é síncrona; ela agenda a atualização e o faz imediatamente dentro do fluxo do seu evento. Contudo, a re-renderização que aplica o novo estado e torna-o visível no seu componente é executada de forma assíncrona em relação ao seu código, ocorrendo somente após o React processar o batch de atualizações.
Chega de teoria. Vamos ver como um sênior resolve isso sem gambiarras.
3. A Solução: O Jeito Sênior de Lidar com o Estado
A solução para esse problema exige uma mudança de mentalidade. É preciso parar de pensar de forma imperativa ("faça isso, depois aquilo") e começar a pensar de forma reativa ("quando este dado mudar, reaja a ele").
Solução #1: O Padrão Reativo com useEffect
A ferramenta canônica do React para lidar com efeitos colaterais (side effects) em resposta a mudanças de estado é o hook useEffect.
import { useState, useEffect } from 'react';
// ...
const [filter, setFilter] = useState('');
// O handler agora só tem uma responsabilidade: atualizar o estado.
function handleFilterChange(newFilterValue: string) {
setFilter(newFilterValue);
}
// Este hook REAGE à mudança na variável 'filter'.
useEffect(() => {
// Opcional: Evita a busca na montagem inicial do componente, caso o filtro comece vazio.
if (filter !== '') {
fetchData(filter);
}
}, [filter]); // O array de dependências garante que o efeito rode APENAS quando 'filter' mudar.
async function fetchData(currentFilter: string) {
console.log(`Buscando dados com o filtro: "${currentFilter}"`);
}Por que funciona? O useEffect é executado por design após o ciclo de renderização ser finalizado. Quando handleFilterChange é chamado, ele agenda uma re-renderização. O React executa essa re-renderização, o novo valor de filter é estabelecido, e só então o useEffect é disparado, agora com acesso ao valor de estado mais recente.
Solução #2: A Abordagem Direta (e Muitas Vezes Melhor)
Para este caso de uso específico, existe uma solução ainda mais simples e performática que não requer um ciclo de renderização adicional.
import { useState } from 'react';
// ...
const [filter, setFilter] = useState('');
function handleFilterChange(newFilterValue: string) {
// O novo valor já existe aqui, em uma variável local. Não precisamos esperar.
const updatedFilter = newFilterValue;
// 1. Agende a re-renderização para que a UI reflita a mudança.
setFilter(updatedFilter);
// 2. Use o novo valor imediatamente para a lógica de negócio.
fetchData(updatedFilter);
}
async function fetchData(currentFilter: string) {
console.log(`Buscando dados com o filtro: "${currentFilter}"`);
}Por que é preferível aqui? Esta abordagem é mais direta e, crucialmente, mais performática. Ela evita a sobrecarga de um ciclo de renderização inteiro e separado que a solução com useEffect exige. Pense bem: a abordagem com useEffect renderiza o componente uma vez, agenda a atualização de estado e só então dispara uma segunda renderização, após a qual o efeito finalmente roda para buscar os dados. A abordagem direta, por outro lado, agenda a atualização da UI com setFilter e dispara a chamada de API com o valor correto, tudo dentro da lógica de um único ciclo de renderização. Simples, eficiente e sem renderizações desnecessárias.
Conclusão
Para evitar cair nessa armadilha novamente, lembre-se dessas regras:
setStateagenda uma re-renderização. Ele não altera a variável na linha de baixo.- O estado é um snapshot. Dentro de uma renderização, seu valor é constante. Lembre-se do conceito de Closure.
- Para REAGIR a uma mudança de estado, use
useEffect. Ele é o gatilho para efeitos colaterais que dependem do estado atualizado. - Se precisar do novo valor IMEDIATAMENTE na mesma função, não use a variável de estado. Use a própria variável/valor que você acabou de passar para a função set.
Se você está pensando em usar setTimeout para 'esperar' o estado atualizar, por favor, pense de novo.
💡 Quer aprender mais sobre Next.js?
Confira meus cursos práticos e aprenda a criar aplicações profissionais do zero.
Ver CursosCurtiu? Compartilhe esse post: