useTransition
useTransition
é um Hook do React que permite você atualizar o state (estado) sem bloquear a UI.
const [isPending, startTransition] = useTransition()
- Referência
- Uso
- Marcando uma atualização de state como uma transição não bloqueante
- Atualizando o componente principal durante uma transição
- Apresentação de um state pendente durante a transição
- Prevenindo indicadores de carregamento indesejados
- Construir um router com suspense ativado
- Exibindo um erro para usuários com um limite de erro
- Solução de problemas
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 atributo
isPending
que informa se há uma Transição pendente - A função
startTransition
que permite marcar uma atualização de estado como uma Transição.
startTransition()
A função startTransition
retornado pelo hook useTransition
permite marcar uma atualização de estado como uma Transição.
function TabContainer() {
const [isPending, startTransition] = useTransition();
const [tab, setTab] = useState('about');
function selectTab(nextTab) {
startTransition(() => {
setTab(nextTab);
});
}
// ...
}
Parâmetros
scope
: Uma função que atualiza o state chamando uma ou mais funçõesset
O React executa imediatamente essa funçãoscope
sem parâmetros e marca todas as atualizações de state agendadas de forma síncrona durante a chamada da funçãoscope
como transições. Essas transições nâo são obrigatórias e 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
deve ser síncrona. O React executa imediatamente essa função, marcando todas as atualizações de state que acontecem enquanto ela é executada como transições. Se você tentar executar mais atualizações de state posteriormente (por exemplo, em um timeout), elas não serão marcadas como transições. -
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 em lotes. Essa é uma limitação que provavelmente será removida em uma versão futura.
Uso
Marcando uma atualização de state como uma transição não bloqueante
Chame useTransition
o nível superior do seu componente para marcar as atualizações de state como Transições sem bloqueio.
import { useState, useTransition } from 'react';
function TabContainer() {
const [isPending, startTransition] = useTransition();
// ...
}
useTransition
retorna um array com exatamente dois itens:
- O sinalizador
isPending
que indica se existe uma transição pendente. - O sinalizador
startTransition
function que permite que você marque uma atualização de state como uma transição.
Pode então marcar uma atualização de state como uma transição desta forma:
function TabContainer() {
const [isPending, startTransition] = useTransition();
const [tab, setTab] = useState('about');
function selectTab(nextTab) {
startTransition(() => {
setTab(nextTab);
});
}
// ...
}
As transições permitem manter as atualizações da interface do usuário com capacidade de resposta, mesmo em dispositivos lentos.
Com uma transição, sua interface do usuário permanece reactiva no meio de uma nova renderização. Por exemplo, se o usuário clicar em uma guia, mas depois mudar de ideia e clicar em outra guia, eles podem fazer isso sem esperar que a primeira renderização termine.
Example 1 of 2: Atualizando a guia atual em uma transição
Neste exemplo, a guia “Posts” é artificialmente retardada para que leve pelo menos um segundo para ser renderizada.
Clique em “Posts” e depois clique imediatamente em “Contato”. Observe que isso interrompe a renderização lenta de “Posts”. A guia “Contato” é exibida imediatamente. Como essa atualização de state é marcada como uma transição, uma lenta re-renderização não congela a interface do usuário.
import { useState, useTransition } 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 [isPending, startTransition] = useTransition(); const [tab, setTab] = useState('about'); function selectTab(nextTab) { startTransition(() => { setTab(nextTab); }); } return ( <> <TabButton isActive={tab === 'about'} onClick={() => selectTab('about')} > Sobre </TabButton> <TabButton isActive={tab === 'posts'} onClick={() => selectTab('posts')} > Posts (slow) </TabButton> <TabButton isActive={tab === 'contact'} onClick={() => selectTab('contact')} > Contato </TabButton> <hr /> {tab === 'about' && <AboutTab />} {tab === 'posts' && <PostsTab />} {tab === 'contact' && <ContactTab />} </> ); }
Atualizando o componente principal durante uma transição
Você também pode atualizar o state de um componente pai a partir da chamada useTransition
. Por exemplo, este componente TabButton
envolve sua lógica de onClick
em uma transição:
export default function TabButton({ children, isActive, onClick }) {
const [isPending, startTransition] = useTransition();
if (isActive) {
return <b>{children}</b>
}
return (
<button onClick={() => {
startTransition(() => {
onClick();
});
}}>
{children}
</button>
);
}
Como o componente pai atualiza seu state dentro do manipulador de eventos onClick
, essa atualização de state é marcada como uma transição. É por isso que, como no exemplo anterior, você pode clicar em “Posts” e imediatamente clicar em “Contact”. A atualização da guia selecionada é marcada como uma transição, então ela não bloqueia as interações do usuário.
import { useTransition } from 'react'; export default function TabButton({ children, isActive, onClick }) { const [isPending, startTransition] = useTransition(); if (isActive) { return <b>{children}</b> } return ( <button onClick={() => { startTransition(() => { onClick(); }); }}> {children} </button> ); }
Apresentação de um state pendente durante a transição
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({ children, isActive, onClick }) {
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({ children, isActive, onClick }) { const [isPending, startTransition] = useTransition(); if (isActive) { return <b>{children}</b> } if (isPending) { return <b className="pending">{children}</b>; } return ( <button onClick={() => { startTransition(() => { onClick(); }); }}> {children} </button> ); }
Prevenindo indicadores de carregamento indesejados
Neste exemplo, o componente PostsTab busca dados usando uma fonte de dados habilitada para Suspense. Quando você clica na guia “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'} onClick={() => setTab('about')} > Sobre </TabButton> <TabButton isActive={tab === 'posts'} onClick={() => setTab('posts')} > Posts </TabButton> <TabButton isActive={tab === 'contact'} onClick={() => setTab('contact')} > Contato </TabButton> <hr /> {tab === 'about' && <AboutTab />} {tab === 'posts' && <PostsTab />} {tab === 'contact' && <ContactTab />} </Suspense> ); }
Ocultar todo o container de guias para mostrar um indicador de carregamento resulta em uma experiência de usuário desagradável. Se você adicionar useTransition
ao TabButton
, pode, em vez disso, indicar o state pendente no próprio botão da guia.
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({ children, isActive, onClick }) { const [isPending, startTransition] = useTransition(); if (isActive) { return <b>{children}</b> } if (isPending) { return <b className="pending">{children}</b>; } return ( <button onClick={() => { startTransition(() => { onClick(); }); }}> {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);
});
}
// ...
This is recommended for two reasons:
- Transições que podem ser interrompidas , permite ao usuário clicar em outro lugar sem precisar esperar que a nova renderização esteja concluída.
- Transições que evitam indicadores de carregamento indesejados, o que permite ao usuário evitar saltos bruscos na navegação.
Aqui está um pequeno exemplo simplificado de um router que utiliza transições para as 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
gerar um erro, você poderá exibir um erro ao seu usuário com um limite de erro. Para usar um limite de erro, envolva o componente onde você está chamando useTransition
em um limite de erro. Depois que a função for passada para erros startTransition
, o substituto para o limite do 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 dessa 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);
Da mesma forma, não é possível marcar uma atualização como uma transição dessa maneira:
startTransition(async () => {
await someAsyncFunction();
// ❌ Configurando um state *depois* da chamada de startTransition
setPage('/about');
});
No entanto, isso funciona em vez disso:
await someAsyncFunction();
startTransition(() => {
// ✅ Configurando state *durante* a chamada de startTransition
setPage('/about');
});
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...
}
}