Async React com NextJS 13
Tags: webdev, react, javascript, braziliandevs
Published: 2023-01-26 06:25:16

Quer ver algo legal? O React está recebendo suporte nativo ao async _e você já pode experimentar_ 😍

Em breve, isso funcionará em qualquer lugar:

```jsx
const  ShowData  =  async  ()  =>  {
  const res =  await  fetch("/data-source")
  const json = res.json()
  return  <p>{json.text}</p>
}
```

Observe o componente React `async`, o `await` em seu corpo, a completa falta de estados, efeitos, ganchos ou bibliotecas de carregamento. Apenas funciona. Você pode usar esse componente em qualquer lugar da sua árvore - mesmo em um componente que ele próprio não é `async`!

Isso faz parte do React's [RFC: Suporte de primeira classe para promessas e async/await](https://github.com/reactjs/rfcs/pull/229). Um próximo passo do React Suspense, sobre o qual escrevi em [React 18 and the future of async data](https://swizec.com/blog/react-18-and-the-future-of-async-data/).

O objetivo é facilitar o uso do React Suspense. Funcionou!

![](https://swizec.com/d3fc4b6bf971e83daa8cef151e4a600e/Parallel-loading-states-in-a-NextJS-13-app1ej3c5.gif)

## Você pode usar o Async React no NextJS 13

No momento, a melhor maneira de experimentar o suporte do Async React é com [o lado beta do NextJS 13](https://beta.nextjs.org/docs) – o `/app` diretório. Eu usei para construir [ScholarStream.ai](https://scholarstream.ai/) e parece estranho, mas funciona muito bem.

Você pode [veer meu código completo no GitHub](https://github.com/Swizec/ScholarStream.ai).

O `/app` diretório faz uso dos [Server Components](https://reactjs.org/blog/2020/12/21/data-fetching-with-react-server-components.html) – componentes React que são renderizados no servidor e enviam HTML. Sem nenhum JavaScript do lado do cliente! Cada re-render volta ao servidor.

Agora eu ouço você pensando _"Mas Swiz, isso é lento como uma merda!? Não inventamos aplicativos de página única porque as viagens de ida e volta ao servidor demoram muito??"_

Sim nós fizemos a volta completa. Mas os servidores percorreram um longo caminho desde então.

Meu entendimento é que a Vercel, empresa por trás do NextJS, faz uso extensivo de funções Serverless e Edge para executar o menor servidor possível o mais próximo possível do usuário para renderizar cada componente. Como o Remix, eles precisam de um compilador personalizado incorporado ao NextJS para fazer esse trabalho sem problemas.

![Renderizadores híbridos com NextJS, imagem de documentos](https://swizec.com/static/75683e2c14957589f757b6249f1be39d/29007/Hybrid-renders-with-NextJS-picture-from-docs9fd18g.png)

Quando um componente do servidor precisa renderizar, uma nova função é executada _apenas para essa renderização_. Com a configuração correta (e pagando por isso), essa função é executada em uma plataforma semelhante a uma CDN, que visa baixa latência. A função retorna _apenas o HTML desse componente_ e NextJS substitui a seção correta da sua interface do usuário no navegador.

Não sei o quão possível/fácil é fazer isso sem a Vercel. Em teoria, o NextJS é uma estrutura autônoma que funciona muito bem sem a Vercel.

Sim, isso significa que, mesmo com os componentes do servidor, ainda resta muito JavaScript do lado do cliente. Mas há menos :)

## Como é o Async React com NextJS beta?

Novamente, código completo no GitHub 👉 [https://github.com/Swizec/ScholarStream.ai](https://github.com/Swizec/ScholarStream.ai). Veja em ação 👉 [ScholarStream.ai](https://scholarstream.ai/)

A base é usar a nova e opinativa estrutura do  NextJS 13 na pasta `/app`:

-   `page.tsx` para a página
-   `layout.tsx` para o layout estático
-   `loading.tsx` para o estado de carregamento

![](https://swizec.com/d19ccb51d79fb9d95b3501cc2ab00c45/Loading-state-gif7add0f.gif)

### page.tsx

[Componentes de página](https://beta.nextjs.org/docs/routing/pages-and-layouts#pages) são sempre componentes do servidor. O NextJS renderiza no servidor, armazena em cache o resultado e retorna.

```jsx
// app/[route]/page.tsx
export default async function Home() {
  return (
    <main className={styles.main}>
      <Pitch />

      <h2>Read about:</h2>
      <TopicsList />

      {/* @ts-expect-error Server Component */}
      <Feed topic="cs.AI" count={5} isLast />
    </main>
  )
}
```

Você precisa dizer ao TypeScript para esperar um erro ao usar components `async` (de servidor) dentro de uma árvore JSX. Funciona, mas os tipos ainda não sabem disso. A equipe do NextJS está trabalhando para atualizar isso atualmente.

`<Feed>` nesse caso, é o componente que executa o carregamento de dados assíncronos. O NextJS lida perfeitamente com esse componente.

### layout.tsx

[Componentes de layout](https://beta.nextjs.org/docs/routing/pages-and-layouts#layouts) diz ao NextJS o que _sempre_ renderiza em torno de sua página. Quando você aninha subdiretórios para fazer rotas complexas, seus layouts também serão aninhados.

```jsx
// app/[route]/layout.tsx
export default function RootLayout({
  // Layouts devem aceitar uma propriedade "children"
  // Ele será preenchido por layouts aninhados ou páginas
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <Script
        src="https://plausible.io/js/script.js"
        data-domain="scholarstream.ai"
      />
      <body>
        <nav className={styles.topNav}>
          <Link href="/about">About</Link>
        </nav>
        {children}
        <div className={styles.footer}>
          built with reckless abandon by <a href="https://swizec.com">Swizec</a>
          <br />
          Thank you to arXiv for use of its open access interoperability.
        </div>
      </body>
    </html>
  )
}
```

### loading.tsx

O [componente de carregamento](https://beta.nextjs.org/docs/routing/loading-ui) renderiza enquanto aguarda a promessa do componente da página.

```jsx
// app/[route]/loading.tsx
export default function PageLoading() {
  return (
    <main className={styles.main}>
      <FeedLoader />
    </main>
  )
}
```

Um desafio interessante aqui, eu não consegui encontrar nenhum componente com uma animação giratória de código aberto que funcionasse. A animação não dispara 🤨.

## Carregando dados

O carregamento de dados é o exemplo típico de uma operação lenta que requer promessas. Mas [você pode usar as mesmas técnicas para qualquer coisa](https://swizec.com/blog/you-can-use-react-query-for-slow-computation-not-just-api/).

Como no React Query, a recomendação é carregar dados perto de onde são usados. No mesmo componente é melhor. Você pode pensar nisso como declarando uma dependência de dados em seu componente e permitindo que React e NextJS lidem com os detalhes.

Por exemplo, aqui está como eu carrego um [feed arXiv](https://arxiv.org/):

```jsx
// carrega lista de artigos
// renderiza em um loop
export const FeedInnards = async (props: FeedProps) => {
  const { offset = 0, count = 10 } = props

  let feed: arxiv.ArxivFeed
  let papers: arxiv.ArxivFeedItem[]

  try {
    feed = await arxiv.getFeed(props.topic)
    papers = feed.items.slice(offset, count)
  } catch (e) {
    console.error(e)

    return (
      <>
        <p>Error loading feed. Try one of these topics instead:</p>
        <TopicsList />
      </>
    )
  }

  return (
    <>
      {papers.map((paper) => (
        // @ts-expect-error Server Component
        <FeedItem paper={paper} key={paper.link} />
      ))}
    </>
  )
}
```

Observe o `await` no corpo desse componente, esse é o novo brinquedo brilhante! React/NextJS mostra um estado de carregamento enquanto a promessa deste componente está pendente. Você não precisa lidar com isso 😍

Você precisa escrever sua própria lógica de `try..catch`, porque [limites de erro](https://reactjs.org/docs/error-boundaries.html) não funcionam com componentes assíncronos. Ainda?

### Busca estendida com cache

Os dados buscados no meu componente `<FeedInnards>` se parece com isso:

```jsx
export async function getFeed(category: string): Promise<ArxivFeed> {
  const parser: Parser<Omit<ArxivFeed, "items">, ArxivFeedItem> = new Parser()
  const feed = await fetch(
    `http://export.arxiv.org/rss/${category}?version=1.0`,
    {
      next: { revalidate: TEN_HOURS },
    }
  ).then((r) => r.text())

  try {
    const parsed = await parser.parseString(feed)
    return parsed
  } catch (e) {
    throw new Error("Could not parse feed")
  }
}
```

Busca o feed RSS do arXiv usando um padrão `fetch()` depois usa um analisador de RSS. Nada louco.

Mas observe os parâmetros extras nessa chamada de busca:

```jsx
await fetch(`http://export.arxiv.org/rss/${category}?version=1.0`, {
  next: { revalidate: TEN_HOURS },
})
```

NextJS [adiciona um parâmetro personalizado para fetch()](https://beta.nextjs.org/docs/api-reference/fetch) que permite especificar um comportamento de cache. Você pode ativar/desativar o cache e especificar o comportamento da revalidação.

No meu caso, o aplicativo busca um novo feed a cada 10 horas. Recarregue a página antes disso e você obterá um resultado estável sem indicadores de carregamento. O cache é estável entre os usuários e a maioria dos visitantes obtém uma página quase estática e rápida.

![](https://swizec.com/d8043583bef066d951a97c966fa5bd75/Fast-page-on-reloadhec276.gif)

Não está claro para mim _onde_ esse cache vive. Isso é algo que funciona com o NextJS ou apenas com a Vercel? 🤔

### Cache sem fetch() ou libs de terceiros

Quando você não controla a chamada API subjacente (como em uma biblioteca), você pode armazenar em cache os resultados usando [o novo método `React.cache()`](https://beta.nextjs.org/docs/data-fetching/caching#per-request-caching). Útil para qualquer operação lenta, porque funciona em funções que retornam promessas, ao invés de alterar a operação do `fetch()` em si.

Por exemplo, quando estou [usando o OpenAI para criar resumos](https://swizec.com/blog/building-apps-with-openai-and-chatgpt/):

```jsx
const getSummary = cache(async (paper: arxiv.ArxivFeedItem) => {
  const summary = await openai.getSummary(paper)
  return summary
})

const PaperSummary = async (props: { paper: arxiv.ArxivFeedItem }) => {
  const summary = await getSummary(props.paper)

  return <p>{summary.choices[0].text}</p>
}
```

Seguindo a idéia de que _"você deve carregar dados perto de onde são usados"_, eu tenho um componente que recebe um `paper`, chama OpenAI para resumir e renderiza um único parágrafo.

A chamada OpenAI está envolvida em `cache()` para aumentar o desempenho. Para otimização de custos, uso adicionalmente o cache Redis na parte superior. Mais sobre isso outra hora :)

## Usando React Suspense para carregamento personalizado

Os estados de carregamento no nível da página são ótimos, mas você pode querer um controle mais refinado. Ou para carregar componentes em paralelo com a renderização otimista de "renderiza o primeiro que chegar".

![](https://swizec.com/d3fc4b6bf971e83daa8cef151e4a600e/Parallel-loading-states-with-suspense-boundaries5384bf.gif)

Você faz cria limites virtuais com o componente `<Suspense>`.

Por exemplo, quando você conhece a parte resumida de um `<FeedItem>` é mais lento que o resto:

```jsx
const FeedItem = async (props: { paper: arxiv.ArxivFeedItem }) => {
  const { paper } = props

  // ...

  return (
    <div className={feedStyles.item}>
      // ...
      <Suspense fallback={<RingLoader color="blue" loading />}>
        {/* @ts-expect-error Server Component */}
        <PaperSummary paper={paper} />
      </Suspense>
      <div>
        Full paper at 👉{" "}
        <a href={paper.link} className={feedStyles.linkPaper}>
          {paper.title.split(/(\(|\. \()arXiv/)[0]}
        </a>
      </div>
    </div>
  )
}
```

Usando o `<Suspense>` é como dizer ao React para _"renderizar o componente `<FeedItem>`, mas mostre um `fallback` enquanto as promessas dentro desta parte da árvore componente estão pendentes"_.

Esses limites virtuais com React Suspense capturará todas as promessas em seus componentes filhos. Você não precisa coordenar nada.

Você pode aumentar os limites virtuais do React Suspense onde quer que faça sentido para o seu aplicativo. Até o estado de carregamento do nível da página é um limite virtual do React Suspense sob o capô.

## Finalizando

Eu amo isso! O Async React simplificará muito do meu código.

Mas estou preocupado que isso seja difícil de implementar fora do NextJS e da Vercel. Veremos.

Saúde,  
~ Swizec

PS: A [documentação beta para o NextJS 13](https://beta.nextjs.org/docs) está fantástica.

---

# Créditos

- Escrito originalmente por [Swizec Teller](https://swizec.com/about/), em [Async React with NextJS 13](https://swizec.com/blog/async-react-with-nextjs-13/).