<Suspense> permite exibir um fallback até que seus filhos terminem de carregar.

<Suspense fallback={<Loading />}>
<SomeComponent />
</Suspense>

Referência

<Suspense>

Props

  • children: A UI real que você pretende renderizar. Se children suspender durante a renderização, o limite do Suspense mudará para renderizar fallback.
  • fallback: Uma UI alternativa para renderizar no lugar da UI real se ela não tiver terminado de carregar. Qualquer nó React válido é aceito, embora na prática, um fallback seja uma visualização de espaço reservado leve, como um indicador de carregamento ou esqueleto. Suspense mudará automaticamente para fallback quando children suspender, e de volta para children quando os dados estiverem prontos. Se fallback suspender durante a renderização, ele ativará o limite do Suspense pai mais próximo.

Ressalvas

  • React não preserva nenhum estado para renderizações que foram suspensas antes de poderem montar pela primeira vez. Quando o componente tiver carregado, React tentará renderizar a árvore suspensa do zero.
  • Se Suspense estava exibindo conteúdo para a árvore, mas então suspendeu novamente, o fallback será mostrado novamente, a menos que a atualização que o causou tenha sido causada por startTransition ou useDeferredValue.
  • Se React precisar ocultar o conteúdo já visível porque ele suspendeu novamente, ele limpará os Effects de layout na árvore de conteúdo. Quando o conteúdo estiver pronto para ser mostrado novamente, React executará os Effects de layout novamente. Isso garante que os Effects que medem o layout do DOM não tentem fazer isso enquanto o conteúdo estiver oculto.
  • React inclui otimizações internas como Streaming Server Rendering e Selective Hydration que são integradas ao Suspense. Leia uma visão geral da arquitetura e assista a uma palestra técnica para saber mais.

Uso

Exibindo um fallback enquanto o conteúdo está carregando

Você pode encapsular qualquer parte de sua aplicação com um limite do Suspense:

<Suspense fallback={<Loading />}>
<Albums />
</Suspense>

React exibirá seu fallback de carregamento até que todo o código e dados necessários para os filhos tenham sido carregados.

No exemplo abaixo, o componente Albums suspende enquanto busca a lista de álbuns. Até que esteja pronto para renderizar, React muda o limite de Suspense mais próximo acima para mostrar o fallback — seu componente Loading. Então, quando os dados carregam, React oculta o fallback Loading e renderiza o componente Albums com dados.

import { Suspense } from 'react';
import Albums from './Albums.js';

export default function ArtistPage({ artist }) {
  return (
    <>
      <h1>{artist.name}</h1>
      <Suspense fallback={<Loading />}>
        <Albums artistId={artist.id} />
      </Suspense>
    </>
  );
}

function Loading() {
  return <h2>🌀 Loading...</h2>;
}

Note

Apenas fontes de dados habilitadas para Suspense ativarão o componente Suspense. Elas incluem:

  • Busca de dados com frameworks habilitados para Suspense como Relay e Next.js
  • Código de componente de carregamento lento com lazy
  • Lendo o valor de uma Promise em cache com use

Suspense não detecta quando os dados são buscados dentro de um Effect ou manipulador de eventos.

A maneira exata de carregar dados no componente Albums acima depende do seu framework. Se você usa um framework habilitado para Suspense, você encontrará os detalhes na documentação de busca de dados dele.

A busca de dados habilitada para Suspense sem o uso de um framework com opinião ainda não é suportada. Os requisitos para implementar uma fonte de dados habilitada para Suspense são instáveis ​​e não documentados. Uma API oficial para integrar fontes de dados com Suspense será lançada em uma versão futura do React.


Revelando o conteúdo juntos de uma vez

Por padrão, toda a árvore dentro de Suspense é tratada como uma única unidade. Por exemplo, mesmo que apenas um desses componentes suspenda esperando por alguns dados, todos eles juntos serão substituídos pelo indicador de carregamento:

<Suspense fallback={<Loading />}>
<Biography />
<Panel>
<Albums />
</Panel>
</Suspense>

Então, depois que todos estiverem prontos para serem exibidos, todos aparecerão juntos de uma vez.

No exemplo abaixo, tanto Biography quanto Albums buscam alguns dados. No entanto, como eles estão agrupados sob um único limite de Suspense, esses componentes sempre “aparecem” juntos ao mesmo tempo.

import { Suspense } from 'react';
import Albums from './Albums.js';
import Biography from './Biography.js';
import Panel from './Panel.js';

export default function ArtistPage({ artist }) {
  return (
    <>
      <h1>{artist.name}</h1>
      <Suspense fallback={<Loading />}>
        <Biography artistId={artist.id} />
        <Panel>
          <Albums artistId={artist.id} />
        </Panel>
      </Suspense>
    </>
  );
}

function Loading() {
  return <h2>🌀 Loading...</h2>;
}

Componentes que carregam dados não precisam ser filhos diretos do limite do Suspense. Por exemplo, você pode mover Biography e Albums para um novo componente Details. Isso não muda o comportamento. Biography e Albums compartilham o mesmo limite de Suspense pai mais próximo, então a revelação deles é coordenada em conjunto.

<Suspense fallback={<Loading />}>
<Details artistId={artist.id} />
</Suspense>

function Details({ artistId }) {
return (
<>
<Biography artistId={artistId} />
<Panel>
<Albums artistId={artistId} />
</Panel>
</>
);
}

Revelando conteúdo aninhado à medida que ele carrega

Quando um componente suspende, o componente Suspense pai mais próximo mostra o fallback. Isso permite que você aninhe vários componentes Suspense para criar uma sequência de carregamento. O fallback de cada limite de Suspense será preenchido à medida que o próximo nível de conteúdo se torna disponível. Por exemplo, você pode dar à lista de álbuns seu próprio fallback:

<Suspense fallback={<BigSpinner />}>
<Biography />
<Suspense fallback={<AlbumsGlimmer />}>
<Panel>
<Albums />
</Panel>
</Suspense>
</Suspense>

Com essa alteração, exibir a Biography não precisa “esperar” o carregamento de Albums.

A sequência será:

  1. Se a Biography ainda não tiver carregado, o BigSpinner será exibido no lugar de toda a área de conteúdo.
  2. Depois que a Biography terminar de carregar, o BigSpinner será substituído pelo conteúdo.
  3. Se Albums ainda não tiver carregado, AlbumsGlimmer será exibido no lugar de Albums e seu pai Panel.
  4. Finalmente, depois que Albums terminar de carregar, ele substituirá AlbumsGlimmer.
import { Suspense } from 'react';
import Albums from './Albums.js';
import Biography from './Biography.js';
import Panel from './Panel.js';

export default function ArtistPage({ artist }) {
  return (
    <>
      <h1>{artist.name}</h1>
      <Suspense fallback={<BigSpinner />}>
        <Biography artistId={artist.id} />
        <Suspense fallback={<AlbumsGlimmer />}>
          <Panel>
            <Albums artistId={artist.id} />
          </Panel>
        </Suspense>
      </Suspense>
    </>
  );
}

function BigSpinner() {
  return <h2>🌀 Loading...</h2>;
}

function AlbumsGlimmer() {
  return (
    <div className="glimmer-panel">
      <div className="glimmer-line" />
      <div className="glimmer-line" />
      <div className="glimmer-line" />
    </div>
  );
}

Limites do Suspense permitem que você coordene quais partes da sua UI devem sempre “aparecer” juntas ao mesmo tempo e quais partes devem revelar progressivamente mais conteúdo em uma sequência de estados de carregamento. Você pode adicionar, mover ou deletar limites de Suspense em qualquer lugar na árvore sem afetar o comportamento do restante do seu aplicativo.

Não coloque um limite de Suspense em todo componente. Os limites de Suspense não devem ser mais granulares do que a sequência de carregamento que você deseja que o usuário experimente. Se você trabalhar com um designer, pergunte a ele onde os estados de carregamento devem ser colocados - é provável que eles já os tenham incluído em seus wireframes de design.


Mostrando conteúdo obsoleto enquanto o conteúdo novo está carregando

Neste exemplo, o componente SearchResults suspende enquanto busca os resultados da pesquisa. Digite "a", espere os resultados e, em seguida, edite-o para "ab". Os resultados de "a" serão substituídos pelo fallback de carregamento.

import { Suspense, useState } from 'react';
import SearchResults from './SearchResults.js';

export default function App() {
  const [query, setQuery] = useState('');
  return (
    <>
      <label>
        Search albums:
        <input value={query} onChange={e => setQuery(e.target.value)} />
      </label>
      <Suspense fallback={<h2>Loading...</h2>}>
        <SearchResults query={query} />
      </Suspense>
    </>
  );
}

Uma alternativa comum para o padrão de UI é adiar a atualização da lista e continuar mostrando os resultados anteriores até que os novos resultados estejam prontos. O Hook useDeferredValue permite que você passe uma versão adiada da consulta:

export default function App() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
return (
<>
<label>
Search albums:
<input value={query} onChange={e => setQuery(e.target.value)} />
</label>
<Suspense fallback={<h2>Loading...</h2>}>
<SearchResults query={deferredQuery} />
</Suspense>
</>
);
}

O query atualizará imediatamente, então a entrada exibirá o novo valor. No entanto, o deferredQuery manterá seu valor anterior até que os dados sejam carregados, então o SearchResults mostrará os resultados obsoletos por um tempo.

Para tornar isso mais óbvio para o usuário, você pode adicionar uma indicação visual quando a lista de resultados obsoletos for exibida:

<div style={{
opacity: query !== deferredQuery ? 0.5 : 1
}}>
<SearchResults query={deferredQuery} />
</div>

Digite "a" no exemplo abaixo, espere os resultados carregarem e, em seguida, edite a entrada para "ab". Observe como, em vez do fallback do Suspense, você agora vê a lista de resultados obsoletos atenuados até que os novos resultados sejam carregados:

import { Suspense, useState, useDeferredValue } from 'react';
import SearchResults from './SearchResults.js';

export default function App() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
  const isStale = query !== deferredQuery;
  return (
    <>
      <label>
        Search albums:
        <input value={query} onChange={e => setQuery(e.target.value)} />
      </label>
      <Suspense fallback={<h2>Loading...</h2>}>
        <div style={{ opacity: isStale ? 0.5 : 1 }}>
          <SearchResults query={deferredQuery} />
        </div>
      </Suspense>
    </>
  );
}

Note

Tanto os deferred values como as Transitions permitem que você evite mostrar o fallback do Suspense em favor de indicadores inline. As Transitions marcam toda a atualização como não urgente, então, tipicamente, elas são usadas por frameworks e bibliotecas de roteamento para navegação. Os deferred values, por outro lado, são mais úteis no código da aplicação, onde você deseja marcar uma parte da UI como não urgente e deixá-la “atrasar” o restante da UI.


Prevenindo que o conteúdo já revelado se esconda

Quando um componente suspende, o limite de Suspense pai mais próximo muda para mostrar o fallback. Isso pode levar a uma experiência do usuário desagradável se ele já estiver exibindo algum conteúdo. Tente pressionar este botão:

import { Suspense, useState } 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('/');

  function navigate(url) {
    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>
      {content}
    </Layout>
  );
}

function BigSpinner() {
  return <h2>🌀 Carregando...</h2>;
}

Quando você pressionou o botão, o componente Router renderizou ArtistPage em vez de IndexPage. Um componente dentro de ArtistPage suspendeu, então, o limite de Suspense mais próximo começou a mostrar o fallback. O limite de Suspense mais próximo estava perto da raiz, então todo o layout do site foi substituído por BigSpinner.

Para evitar isso, você pode marcar a atualização de estado da navegação como uma Transition com startTransition:

function Router() {
const [page, setPage] = useState('/');

function navigate(url) {
startTransition(() => {
setPage(url);
});
}
// ...

Isso informa ao React que a transição de estado não é urgente e é melhor continuar mostrando a página anterior em vez de ocultar qualquer conteúdo já revelado. Agora, clicar no botão “espera” o carregamento da Biography:

import { Suspense, startTransition, useState } 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('/');

  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>
      {content}
    </Layout>
  );
}

function BigSpinner() {
  return <h2>🌀 Carregando...</h2>;
}

Uma Transição não espera que todo o conteúdo seja carregado. Ela espera apenas o tempo suficiente para evitar a ocultação do conteúdo já revelado. Por exemplo, o Layout do site já foi revelado, então seria ruim escondê-lo atrás de um spinner de carregamento. No entanto, o limite de Suspense aninhado em torno de Albums é novo, então a Transição não espera por ele.

Note

Espera-se que roteadores compatíveis com Suspense envolvam as atualizações de navegação em Transitions por padrão.


Indicando que uma Transition está acontecendo

No exemplo acima, assim que você clica no botão, não há nenhuma indicação visual de que uma navegação está em andamento. Para adicionar um indicador, você pode substituir startTransition por useTransition que fornece um valor booleano isPending. No exemplo abaixo, ele é usado para alterar o estilo do cabeçalho do site enquanto uma Transition está acontecendo:

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


Resetando limites de Suspense na navegação

Durante uma Transition, o React evitará ocultar o conteúdo já revelado. No entanto, se você navegar para uma rota com parâmetros diferentes, você pode querer dizer ao React que é um conteúdo diferente. Você pode expressar isso com uma key:

<ProfilePage key={queryParams.id} />

Imagine que você está navegando dentro da página de perfil de um usuário e algo suspende. Se essa atualização estiver envolvida em uma Transition, ela não acionará o fallback para o conteúdo já visível. Esse é o comportamento esperado.

No entanto, agora imagine que você está navegando entre dois perfis de usuário diferentes. Nesse caso, faz sentido mostrar o fallback. Por exemplo, a linha do tempo de um usuário é um conteúdo diferente da linha do tempo de outro usuário. Especificando uma key, você garante que o React trate os perfis de usuários diferentes como componentes diferentes e redefine os limites de Suspense durante a navegação. Roteadores integrados ao Suspense devem fazer isso automaticamente.


Fornecendo um fallback para erros no servidor e conteúdo somente do cliente

Se você usar uma das APIs de renderização no servidor de streaming (ou um framework que dependa delas), React também usará seus limites de <Suspense> para lidar com erros no servidor. Se um componente lançar um erro no servidor, o React não abortará a renderização do servidor. Em vez disso, ele encontrará o componente <Suspense> mais próximo acima dele e incluirá seu fallback (como um spinner) no HTML do servidor gerado. O usuário verá um spinner no início.

No cliente, o React tentará renderizar o mesmo componente novamente. Se ele também gerar erros no cliente, o React lançará o erro e exibirá o limite de erro mais próximo. No entanto, se não gerar erros no cliente, o React não exibirá o erro ao usuário, pois o conteúdo foi exibido com sucesso.

Você pode usar isso para excluir alguns componentes da renderização no servidor. Para fazer isso, lance um erro no ambiente do servidor e, em seguida, envolva-os em um limite de <Suspense> para substituir seu HTML por fallbacks:

<Suspense fallback={<Loading />}>
<Chat />
</Suspense>

function Chat() {
if (typeof window === 'undefined') {
throw Error('Chat should only render on the client.');
}
// ...
}

O HTML do servidor incluirá o indicador de carregamento. Ele será substituído pelo componente Chat no cliente.


Solução de problemas

Como evito que a UI seja substituída por um fallback durante uma atualização?

Substituir a UI visível por um fallback cria uma experiência do usuário desagradável. Isso pode acontecer quando uma atualização faz com que um componente suspenda, e o limite de Suspense mais próximo já está mostrando conteúdo ao usuário.

Para evitar que isso aconteça, marque a atualização como não urgente usando startTransition. Durante uma Transition, o React aguardará até que dados suficientes tenham sido carregados para impedir o aparecimento de um fallback indesejado:

function handleNextPageClick() {
// Se esta atualização suspender, não oculte o conteúdo já exibido
startTransition(() => {
setCurrentPage(currentPage + 1);
});
}

Isso evitará ocultar o conteúdo existente. No entanto, quaisquer limites de Suspense recém-renderizados ainda exibirão imediatamente fallbacks para evitar bloquear a UI e permitir que o usuário veja o conteúdo conforme ele se torna disponível.

O React só evitará fallbacks indesejados durante atualizações não urgentes. Ele não atrasará uma renderização se for o resultado de uma atualização urgente. Você deve aceitar com uma API como startTransition ou useDeferredValue.

Se seu roteador estiver integrado ao Suspense, ele deverá envolver suas atualizações em startTransition automaticamente.