useTransition
useTransition
é um Hook do React que permite renderizar uma parte da interface do usuário em segundo plano.
const [isPending, startTransition] = useTransition()
- Referência
- Uso
- Solução de problemas
- A atualização de uma entrada numa transição não funciona
- O React não está tratando minha atualização de state como uma transição
- O React não trata minha atualização de estado após
await
como uma Transição - Desejo acessar chamar
useTransition
de fora de um componente. - A função que passo para
startTransition
é executada imediatamente. - Minhas atualizações de estado em Transições estão fora de ordem
Referência
useTransition()
Chame useTransition
no nível superior do seu componente para marcar algumas atualizações de estado como Transições.
import { useTransition } from 'react';
function TabContainer() {
const [isPending, startTransition] = useTransition();
// ...
}
Abaixo, você encontrará mais exemplos.
Parâmetros
useTransition
não recebe parâmetros.
Retornos
useTransition
retorna um array com exatamente dois itens:
- O sinalizador
isPending
que indica se existe uma Transição pendente. - A função
startTransition
que permite marcar atualizações como uma Transição.
startTransition(action)
A função startTransition
retornada por useTransition
permite marcar uma atualização como uma Transição.
function TabContainer() {
const [isPending, startTransition] = useTransition();
const [tab, setTab] = useState('about');
function selectTab(nextTab) {
startTransition(() => {
setTab(nextTab);
});
}
// ...
}
Parâmetros
action
: Uma função que atualiza algum estado chamando uma ou mais funçõesset
. O React chamaaction
imediatamente sem parâmetros e marca todas as atualizações de estado agendadas de forma síncrona durante a chamada da funçãoaction
como Transições. Quaisquer chamadas assíncronas que sejam aguardadas naaction
serão incluídas na Transição, mas atualmente requerem envolver quaisquer funçõesset
após oawait
em umstartTransition
adicional (veja Solução de Problemas). Atualizações de estado marcadas como Transições serão não bloqueantes e não exibirão indicadores de carregamento indesejados.
Retorno
startTransition
não possui valor de retorno.
Caveats
-
useTransition
é um Hook, portanto, deve ser chamado dentro de componentes ou Hooks personalizados. Se você precisar iniciar uma transição em outro lugar (por exemplo, a partir de uma biblioteca de dados), chame a função independentestartTransition
em vez disso. -
Você só pode envolver uma atualização em uma transição se tiver acesso à função
set
daquele state. Se você deseja iniciar uma transição em resposta a alguma propriedade ou valor de um Hook personalizado, tente utilizaruseDeferredValue
em vez disso. -
A função que você passa para
startTransition
é chamada imediatamente, marcando todas as atualizações de estado que acontecem enquanto ela é executada como Transições. Se você tentar realizar atualizações de estado em umsetTimeout
, por exemplo, elas não serão marcadas como Transições. -
Você deve envolver quaisquer atualizações de estado após qualquer solicitação assíncrona em outro
startTransition
para marcá-las como Transições. Esta é uma limitação conhecida que iremos corrigir no futuro (veja Solução de Problemas). -
A função
startTransition
tem uma identidade estável, então você frequentemente verá ela omitida das dependências de Efeito, mas incluí-la não fará com que o Efeito seja disparado. Se o linter permitir que você omita uma dependência sem erros, é seguro fazê-lo. Saiba mais sobre remover dependências de Efeito. -
Uma atualização de state marcada como uma transição pode ser interrompida por outras atualizações de state. Por exemplo, se você atualizar um componente de gráfico dentro de uma transição, mas depois começar a digitar em uma entrada enquanto o gráfico estiver no meio de uma nova renderização, o React reiniciará o trabalho de renderização no componente de gráfico após lidar com a atualização da entrada.
-
As atualizações de transição não podem ser usadas para controlar entradas de texto.
-
Se houver várias Transições em andamento, o React atualmente as agrupa. Esta é uma limitação que pode ser removida em uma versão futura.
Uso
Realizar atualizações não bloqueantes com Ações
Chame useTransition
no topo do seu componente para criar Ações e acessar o estado pendente:
import {useState, useTransition} from 'react';
function CheckoutForm() {
const [isPending, startTransition] = useTransition();
// ...
}
useTransition
retorna um array com exatamente dois itens:
- O sinalizador
isPending
que indica se existe uma Transição pendente. - A função
startTransition
que permite criar uma Ação.
Para iniciar uma Transição, passe uma função para startTransition
assim:
import {useState, useTransition} from 'react';
import {updateQuantity} from './api';
function CheckoutForm() {
const [isPending, startTransition] = useTransition();
const [quantity, setQuantity] = useState(1);
function onSubmit(newQuantity) {
startTransition(async function () {
const savedQuantity = await updateQuantity(newQuantity);
startTransition(() => {
setQuantity(savedQuantity);
});
});
}
// ...
}
A função passada para startTransition
é chamada de “Ação”. Você pode atualizar o estado e (opcionalmente) realizar efeitos colaterais dentro de uma Ação, e o trabalho será feito em segundo plano sem bloquear as interações do usuário na página. Uma Transição pode incluir várias Ações, e enquanto uma Transição está em andamento, sua interface do usuário permanece responsiva. Por exemplo, se o usuário clicar em uma aba, mas depois mudar de ideia e clicar em outra aba, o segundo clique será imediatamente tratado sem esperar que a primeira atualização termine.
Para dar feedback ao usuário sobre Transições em andamento, o estado isPending
muda para true
na primeira chamada para startTransition
, e permanece true
até que todas as Ações sejam concluídas e o estado final seja mostrado ao usuário. As Transições garantem que os efeitos colaterais nas Ações sejam concluídos em ordem para prevenir indicadores de carregamento indesejados, e você pode fornecer feedback imediato enquanto a Transição está em andamento com useOptimistic
.
Example 1 of 2: Atualizando a quantidade em uma Ação
Neste exemplo, a função updateQuantity
simula uma solicitação ao servidor para atualizar a quantidade do item no carrinho. Esta função é artificialmente desacelerada para que leve pelo menos um segundo para concluir a solicitação.
Atualize a quantidade várias vezes rapidamente. Observe que o estado pendente “Total” é mostrado enquanto qualquer solicitação está em andamento, e o “Total” é atualizado apenas após a solicitação final ser concluída. Como a atualização está em uma Ação, a “quantidade” pode continuar a ser atualizada enquanto a solicitação está em andamento.
import { useState, useTransition } from "react"; import { updateQuantity } from "./api"; import Item from "./Item"; import Total from "./Total"; export default function App({}) { const [quantity, setQuantity] = useState(1); const [isPending, startTransition] = useTransition(); const updateQuantityAction = async newQuantity => { // To access the pending state of a transition, // call startTransition again. startTransition(async () => { const savedQuantity = await updateQuantity(newQuantity); startTransition(() => { setQuantity(savedQuantity); }); }); }; return ( <div> <h1>Checkout</h1> <Item action={updateQuantityAction}/> <hr /> <Total quantity={quantity} isPending={isPending} /> </div> ); }
This is a basic example to demonstrate how Actions work, but this example does not handle requests completing out of order. When updating the quantity multiple times, it’s possible for the previous requests to finish after later requests causing the quantity to update out of order. This is a known limitation that we will fix in the future (see Troubleshooting below).
For common use cases, React provides built-in abstractions such as:
These solutions handle request ordering for you. When using Transitions to build your own custom hooks or libraries that manage async state transitions, you have greater control over the request ordering, but you must handle it yourself.
Expondo a prop action
de componentes
Você pode expor uma prop action
de um componente para permitir que um componente pai chame uma Ação.
Por exemplo, este componente TabButton
envolve sua lógica onClick
em uma prop action
:
export default function TabButton({ action, children, isActive }) {
const [isPending, startTransition] = useTransition();
if (isActive) {
return <b>{children}</b>
}
return (
<button onClick={() => {
startTransition(() => {
action();
});
}}>
{children}
</button>
);
}
Como o componente pai atualiza seu estado dentro da action
, essa atualização de estado é marcada como uma Transição. Isso significa que você pode clicar em “Posts” e, em seguida, clicar imediatamente em “Contato” sem bloquear as interações do usuário:
import { useTransition } from 'react'; export default function TabButton({ action, children, isActive }) { const [isPending, startTransition] = useTransition(); if (isActive) { return <b>{children}</b> } return ( <button onClick={() => { startTransition(() => { action(); }); }}> {children} </button> ); }
Exibindo um estado visual pendente
Você pode usar o valor booleano isPending
retornado por useTransition
para indicar ao usuário que uma transição está em andamento. Por exemplo, o botão da guia pode ter um state visual especial de “pendente”.
function TabButton({ action, children, isActive }) {
const [isPending, startTransition] = useTransition();
// ...
if (isPending) {
return <b className="pending">{children}</b>;
}
// ...
Observe como clicar em “Publicações” agora parece mais reativo, porque o botão da guia em si é atualizado imediatamente:
import { useTransition } from 'react'; export default function TabButton({ action, children, isActive }) { const [isPending, startTransition] = useTransition(); if (isActive) { return <b>{children}</b> } if (isPending) { return <b className="pending">{children}</b>; } return ( <button onClick={() => { startTransition(() => { action(); }); }}> {children} </button> ); }
Prevenindo indicadores de carregamento indesejados
Neste exemplo, o componente PostsTab
busca alguns dados usando use. Quando você clica na aba “Posts”, o componente PostsTab
suspende, fazendo com que o fallback de carregamento mais próximo apareça:
import { Suspense, useState } from 'react'; import TabButton from './TabButton.js'; import AboutTab from './AboutTab.js'; import PostsTab from './PostsTab.js'; import ContactTab from './ContactTab.js'; export default function TabContainer() { const [tab, setTab] = useState('about'); return ( <Suspense fallback={<h1>🌀 Carregando...</h1>}> <TabButton isActive={tab === 'about'} action={() => setTab('about')} > Sobre </TabButton> <TabButton isActive={tab === 'posts'} action={() => setTab('posts')} > Posts </TabButton> <TabButton isActive={tab === 'contact'} action={() => setTab('contact')} > Contato </TabButton> <hr /> {tab === 'about' && <AboutTab />} {tab === 'posts' && <PostsTab />} {tab === 'contact' && <ContactTab />} </Suspense> ); }
Ocultar todo o contêiner da aba para mostrar um indicador de carregamento leva a uma experiência de usuário brusca. Se você adicionar useTransition
ao TabButton
, você pode exibir o estado pendente no próprio botão da aba.
Observe que clicar em “Posts” já não substitui mais o contêiner da guia inteira por um indicador de carregamento:
import { useTransition } from 'react'; export default function TabButton({ action, children, isActive }) { const [isPending, startTransition] = useTransition(); if (isActive) { return <b>{children}</b> } if (isPending) { return <b className="pending">{children}</b>; } return ( <button onClick={() => { startTransition(() => { action(); }); }}> {children} </button> ); }
Read more about using Transitions with Suspense.
Construir um router com suspense ativado
Se estiver a construir uma estrutura React ou um router, recomendamos marcar as navegações de página como transições.
function Router() {
const [page, setPage] = useState('/');
const [isPending, startTransition] = useTransition();
function navigate(url) {
startTransition(() => {
setPage(url);
});
}
// ...
Isso é recomendado por três razões:
- As transições são interrompíveis, o que permite que o usuário clique em outro lugar sem esperar a re-renderização ser concluída.
- As transições evitam indicadores de carregamento indesejados, o que permite que o usuário evite saltos bruscos na navegação.
- As transições esperam por todas as ações pendentes o que permite que o usuário espere que os efeitos colaterais sejam concluídos antes que a nova página seja exibida.
Aqui está um exemplo simplificado de roteador usando Transições para navegações.
import { Suspense, useState, useTransition } from 'react'; import IndexPage from './IndexPage.js'; import ArtistPage from './ArtistPage.js'; import Layout from './Layout.js'; export default function App() { return ( <Suspense fallback={<BigSpinner />}> <Router /> </Suspense> ); } function Router() { const [page, setPage] = useState('/'); const [isPending, startTransition] = useTransition(); function navigate(url) { startTransition(() => { setPage(url); }); } let content; if (page === '/') { content = ( <IndexPage navigate={navigate} /> ); } else if (page === '/the-beatles') { content = ( <ArtistPage artist={{ id: 'the-beatles', name: 'The Beatles', }} /> ); } return ( <Layout isPending={isPending}> {content} </Layout> ); } function BigSpinner() { return <h2>🌀 Carregando...</h2>; }
Exibindo um erro para usuários com um limite de erro
Se uma função passada para startTransition
lançar um erro, você pode exibir um erro para o seu usuário com um limite de erro. Para usar um limite de erro, envolva o componente onde você está chamando o useTransition
em um limite de erro. Uma vez que a função passada para startTransition
gerar um erro, o fallback para o limite de erro será exibido.
import { useTransition } from "react"; import { ErrorBoundary } from "react-error-boundary"; export function AddCommentContainer() { return ( <ErrorBoundary fallback={<p>⚠️Something went wrong</p>}> <AddCommentButton /> </ErrorBoundary> ); } function addComment(comment) { // Para fins de demonstração, para mostrar o limite de erro if (comment == null) { throw new Error("Example Error: An error thrown to trigger error boundary"); } } function AddCommentButton() { const [pending, startTransition] = useTransition(); return ( <button disabled={pending} onClick={() => { startTransition(() => { // Intentionally not passing a comment // so error gets thrown addComment(); }); }} > Add comment </button> ); }
Solução de problemas
A atualização de uma entrada numa transição não funciona
Não é possível utilizar uma transição para uma variável de state que controla uma entrada:
const [text, setText] = useState('');
// ...
function handleChange(e) {
// ❌ Não é possível utilizar transições ao uma entrada de state controlada.
startTransition(() => {
setText(e.target.value);
});
}
// ...
return <input value={text} onChange={handleChange} />;
Isso ocorre porque as transições não são bloqueantes, mas a atualização de uma entrada em resposta ao evento de alteração deve ocorrer de forma síncrona. Se você deseja executar uma transição em resposta à digitação, você tem duas opções:
- Você pode declarar duas variáveis de state separadas: uma para o state da entrada (que sempre é atualizado de forma síncrona) e outra que você atualizará em uma transição. Isso permite que você controle a entrada usando o state síncrono e passe a variável de state de transição (que ficará “atrasada” em relação à entrada) para o restante da sua lógica de renderização.
- Em alternativa, pode ter uma variável de state e adicionar
useDeferredValue
que ficará “atrasado” em relação ao valor real. Isso irá desencadear re-renderizações não bloqueantes para “alcançar” automaticamente o novo valor.
O React não está tratando minha atualização de state como uma transição
Quando você envolve uma atualização de state em uma transição, certifique-se de que ela ocorra durante a chamada de startTransition
:
startTransition(() => {
// ✅ Configurando o state *durante* a chamada de startTransition
setPage('/about');
});
A função que você passa para startTransition
deve ser síncrona. Você não pode marcar uma atualização como uma Transição desta forma:
startTransition(() => {
// ❌ Configurando o state *após* a chamada de startTransition
setTimeout(() => {
setPage('/about');
}, 1000);
});
Em vez disso, você pode fazer o seguinte:
setTimeout(() => {
startTransition(() => {
// ✅ Configurando st *durante* a chamada de startTransition.
setPage('/about');
});
}, 1000);
O React não trata minha atualização de estado após await
como uma Transição
Quando você usa await
dentro de uma função startTransition
, as atualizações de estado que acontecem após o await
não são marcadas como Transições. Você deve envolver as atualizações de estado após cada await
em uma chamada startTransition
:
startTransition(async () => {
await someAsyncFunction();
// ❌ Not using startTransition after await
setPage('/about');
});
No entanto, isso funciona em vez disso:
startTransition(async () => {
await someAsyncFunction();
// ✅ Using startTransition *after* await
startTransition(() => {
setPage('/about');
});
});
Esta é uma limitação do JavaScript devido ao React perder o escopo do contexto assíncrono. No futuro, quando o AsyncContext estiver disponível, essa limitação será removida.
Desejo acessar chamar useTransition
de fora de um componente.
Você não pode chamar useTransition
fora de um componente porque ele é um Hook. Neste caso, utilize o método independente startTransition
Ele funciona da mesma forma, mas não fornece o indicador isPending
.
A função que passo para startTransition
é executada imediatamente.
Se você executar este código, ele imprimirá 1, 2, 3:
console.log(1);
startTransition(() => {
console.log(2);
setPage('/about');
});
console.log(3);
Espera-se que imprima 1, 2, 3. A função passada para startTransition
não sofre atrasos. Ao contrário do setTimeout
, que não é executada posteriormente. O React executa sua função imediatamente, mas qualquer atualização de state agendada enquanto ele está em execução são marcadas como transições. Você pode imaginar que funciona assim:
// Uma versão simplificada de como o React funciona
let isInsideTransition = false;
function startTransition(scope) {
isInsideTransition = true;
scope();
isInsideTransition = false;
}
function setState() {
if (isInsideTransition) {
// ... Agendar uma atualização do state de transição...
} else {
// ... Agendar uma atualização urgente do state...
}
}
Minhas atualizações de estado em Transições estão fora de ordem
Se você usar await
dentro de startTransition
, pode ver as atualizações acontecerem fora de ordem.
Neste exemplo, a função updateQuantity
simula uma solicitação ao servidor para atualizar a quantidade do item no carrinho. Esta função artificialmente retorna todas as outras solicitações após a anterior para simular condições de corrida para solicitações de rede.
Tente atualizar a quantidade uma vez, depois atualize rapidamente várias vezes. Você pode ver o total incorreto:
import { useState, useTransition } from "react"; import { updateQuantity } from "./api"; import Item from "./Item"; import Total from "./Total"; export default function App({}) { const [quantity, setQuantity] = useState(1); const [isPending, startTransition] = useTransition(); // Store the actual quantity in separate state to show the mismatch. const [clientQuantity, setClientQuantity] = useState(1); const updateQuantityAction = newQuantity => { setClientQuantity(newQuantity); // Access the pending state of the transition, // by wrapping in startTransition again. startTransition(async () => { const savedQuantity = await updateQuantity(newQuantity); startTransition(() => { setQuantity(savedQuantity); }); }); }; return ( <div> <h1>Checkout</h1> <Item action={updateQuantityAction}/> <hr /> <Total clientQuantity={clientQuantity} savedQuantity={quantity} isPending={isPending} /> </div> ); }
Quando clicar várias vezes, é possível que as solicitações anteriores sejam concluídas após as solicitações posteriores. Quando isso acontece, o React atualmente não tem como saber a ordem pretendida. Isso ocorre porque as atualizações são agendadas de forma assíncrona, e o React perde o contexto da ordem através do limite assíncrono.
Isso é esperado, porque Ações dentro de uma Transição não garantem a ordem de execução. Para casos de uso comuns, o React fornece abstrações de nível superior como useActionState
e ações de <form>
que lidam com a ordenação para você. Para casos de uso avançados, você precisará implementar sua própria lógica de fila e abortar para lidar com isso.