useTransition é um Hook do React que permite renderizar uma parte da interface do usuário em segundo plano.

const [isPending, startTransition] = useTransition()

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:

  1. O sinalizador isPending que indica se existe uma Transição pendente.
  2. 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);
});
}
// ...
}

Note

Funções chamadas em startTransition são chamadas de “Ações”.

A função passada para startTransition é chamada de “Ação”. Por convenção, qualquer callback chamado dentro de startTransition (como um callback prop) deve ser nomeado como action ou incluir o sufixo “Action”:

function SubmitButton({ submitAction }) {
const [isPending, startTransition] = useTransition();

return (
<button
disabled={isPending}
onClick={() => {
startTransition(() => {
submitAction();
});
}}
>
Submit
</button>
);
}

Parâmetros

  • action: Uma função que atualiza algum estado chamando uma ou mais funções set. O React chama action imediatamente sem parâmetros e marca todas as atualizações de estado agendadas de forma síncrona durante a chamada da função action como Transições. Quaisquer chamadas assíncronas que sejam aguardadas na action serão incluídas na Transição, mas atualmente requerem envolver quaisquer funções set após o await em um startTransition 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 independente startTransition 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 utilizar useDeferredValue 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 um setTimeout, 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:

  1. O sinalizador isPending que indica se existe uma Transição pendente.
  2. 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.

A diferença entre Ações e manipulação de eventos regular

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.

Note

Transições só “esperam” o suficiente para evitar ocultar o conteúdo já revelado (como o contêiner da aba). Se a aba de Posts tivesse um limite <Suspense> aninhado, a Transição não “esperaria” por ele.


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:

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>;
}

Note

Suspense-enabled Espera-se que os roteadores envolvam automaticamente as atualizações de navegação em transições por padrão.


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:

  1. 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.
  2. 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.