useTransition é um Hook do React que permite você atualizar o state (estado) sem bloquear a UI.

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 atributo isPending que informa se há uma Transição pendente
  2. 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ções set O React executa imediatamente essa função scope sem parâmetros e marca todas as atualizações de state agendadas de forma síncrona durante a chamada da função scope 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 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 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:

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

A diferença entre o uso de useTransition e atualizações regulares de state

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.

Note

As transições só “esperam” o tempo tempo necessário para evitar ocultação já revelada com conteúdo (como o contêiner da guia). Se a guia Posts tivesse um limite tivesse um limite <Suspense>, 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);
});
}
// ...

This is recommended for two reasons:

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

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

Canary

Atualmente, o limite de erro para useTransition está disponível apenas nos canais canário e experimental do React. Saiba mais sobre canais de lançamento do React aqui.

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:

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