useTransition permite marcar uma atualização de estado como não urgente. O React renderiza essa atualização em segundo plano e a descarta ou recomeça se acontecer algo mais urgente, como um clique ou uma tecla, então a página continua responsiva enquanto uma parte lenta da tela se atualiza.
Clique em posts e, antes de a lista aparecer, clique em contact. O contact aparece na hora: o React descartou a renderização dos posts pela metade. Agora troque startTransition(() => { setTab(next); }) por um simples setTab(next); e tente a mesma coisa: a página congela por cerca de 300 ms depois do clique em posts, e o seu clique em contact espera os posts terminarem.
A sintaxe
const [isPending, startTransition] = useTransition();
startTransition(() => {
setTab(next);
});
startTransitionrecebe uma função e a executa na hora. Todas as atualizações de estado dentro dela são marcadas como transições.isPendingétruedesde o momento em que você inicia a transição até o React terminar de renderizá-la e aplicar o resultado.
O useTransition não recebe argumentos. A função que você passa executa de forma síncrona; a transição diz respeito a como o React renderiza a atualização resultante, não a quando a sua função executa.
O que significa "não urgente"
O React divide as atualizações em dois tipos. Atualizações urgentes, como digitar, clicar e pressionar, precisam aparecer na hora ou a página parece quebrada. Atualizações de transição, como trocar para uma nova visualização ou mostrar resultados filtrados, podem levar um momento.
O React renderiza uma transição em pequenas fatias e confere entre os componentes se chegou algo urgente. Se chegou, o React pausa ou abandona a transição, trata a atualização urgente e então recomeça a transição com o estado mais recente. Enquanto uma transição renderiza, a tela anterior continua visível; o React não mostra um resultado montado pela metade. É por isso que o exemplo continua mostrando o texto do about, com a dica Loading ao lado dos botões, até os posts ficarem prontos.
A interrupção acontece entre componentes. Um único componente que bloqueia por 300 ms não pode ser cortado ao meio, e é por isso que o trabalho lento acima está espalhado por 300 pequenos componentes SlowPost.
Mostrando uma dica de pendência
O isPending é o lugar para um indicador de carregamento que não substitui o conteúdo atual: um spinner ao lado do botão, um texto esmaecido, um rótulo "Saving...". No primeiro exemplo ele mostra Loading... ao lado das abas. Você também pode usá-lo para estilizar o conteúdo:
<div style={{ opacity: isPending ? 0.6 : 1 }}>{content}</div>
Filtrando uma lista grande enquanto digita
Você não pode colocar o próprio valor de um input em uma transição. Um <input> controlado precisa receber o novo valor na mesma atualização urgente, ou caracteres se perdem. Mantenha duas partes de estado: o texto do input (urgente) e a busca que a lista usa (uma transição).
Digite 123 rápido. O input nunca trava. O Console ainda registra uma linha rendering results por tecla, porque Results registra assim que o React começa a renderizá-lo, mas as listas para 1 e 12 nunca chegam à tela: cada tecla mais nova interrompeu a renderização em andamento, e o React recomeçou com a busca mais recente. Results está envolvido em memo de propósito: sem isso, a renderização urgente de cada tecla também renderizaria a lista lenta com a busca antiga, e você perderia o benefício.
startTransition sem o hook
O react também exporta o startTransition como uma função comum. Ele marca as atualizações como transições do mesmo jeito que o hook, mas você não recebe o isPending. Use-o onde você não pode chamar um hook, por exemplo em uma biblioteca de dados ou em um roteador fora de qualquer componente.
import { startTransition } from 'react';
function navigate(url) {
startTransition(() => {
setCurrentUrl(url);
});
}
Transições assíncronas no React 19
Desde o React 19 a função que você passa pode ser async. O isPending então fica true durante todo o tempo em que a função executa, incluindo a espera, o que é um jeito simples de mostrar um estado pendente durante uma requisição. O saveName falso abaixo faz o papel de uma requisição real.
Repare no segundo startTransition em volta de setSaved. O React perde o rastro da transição através de um await, então uma atualização de estado depois dele precisa ser envolvida de novo para contar como parte da transição. O React chama uma função passada assim de action; o useActionState e as form actions se apoiam no mesmo mecanismo.
Uma transição não é um debounce
Um debounce espera um tempo fixo depois da última tecla antes de fazer qualquer coisa. Uma transição não espera. Ela começa a renderizar na hora e, se chega uma atualização mais nova, descarta o trabalho antigo. Em um aparelho rápido o resultado aparece sem atraso nenhum; em um lento, o React faz o máximo que consegue entre as teclas. O que uma transição não consegue é reduzir o número de requisições de rede: se cada tecla inicia um fetch, faça o debounce do fetch.
Transições e Suspense
As transições também mudam o que um boundary de Suspense faz. Normalmente, quando um componente dentro de um boundary suspende (um componente lazy carregando, ou dados lidos com use), o React substitui o conteúdo pelo fallback. Se a atualização que causou a suspensão foi uma transição, o React mantém o conteúdo que já está na tela, e o isPending fica true até o novo conteúdo ficar pronto. É assim que os roteadores evitam piscar um spinner do tamanho da página a cada navegação: eles envolvem a navegação em uma transição, e a página antiga continua visível até a próxima poder renderizar. Conteúdo que nunca foi mostrado continua usando o fallback, então um boundary recém-revelado se comporta como de costume.
O que não funciona
- Atualizações dentro de um timeout.
startTransition(() => setTimeout(() => setTab(next), 100))não marca a atualização, porque ela executa depois que ostartTransitionjá retornou. Coloque ostartTransitiondentro do timeout. - Um único componente lento. O React só consegue interromper entre componentes, então um componente que bloqueia por muito tempo continua bloqueando.
- Controlar um input. Como acima, o próprio estado do input continua urgente.
Para o caso em que você não é dono do setter de estado e só recebe um valor, como uma prop, o useDeferredValue dá a você o mesmo comportamento.
Perguntas frequentes
O que o useTransition faz?
Ele dá a você o startTransition, que marca as atualizações de estado dentro dele como não urgentes, e o isPending, que é true enquanto essa atualização ainda está renderizando. O React pode interromper uma renderização não urgente para tratar primeiro cliques e digitação.
Qual é a diferença entre useTransition e startTransition?
Os dois marcam atualizações como transições. O hook useTransition também dá a você o isPending para uma dica de carregamento. O startTransition avulso importado de react funciona fora de componentes, mas não tem indicador de pendência.
Posso usar o useTransition para um input de texto?
Não para o próprio valor do input. Um input controlado precisa atualizar de forma síncrona, ou a digitação quebra. Mantenha o estado do input urgente e coloque em uma transição a atualização cara que depende dele, ou use o useDeferredValue.
O useTransition é o mesmo que debounce?
Não. Um debounce espera um tempo fixo antes de fazer qualquer coisa. Uma transição começa a renderizar na hora e é interrompida se chega uma atualização mais nova, então em um aparelho rápido o resultado aparece sem atraso nenhum.
O startTransition pode receber uma função async?
Sim, desde o React 19. O isPending fica true até a função assíncrona terminar. Atualizações de estado depois de um await precisam ser envolvidas em outra chamada de startTransition para serem marcadas como transição.