Dados assíncronos

Dados assíncronos

O createResource transforma uma promise em signal. Combine com o Suspense para estados de carregamento e com o ErrorBoundary para falhas.

createResource

tsx
import { createResource } from '@velozjs/dom';

// Sem fonte: busca uma vez.
const [user] = createResource(() => fetch('/api/me').then((r) => r.json()));

// Com fonte: busca de novo sempre que ela muda.
const [post] = createResource(postId, (id) => fetch(`/api/posts/${id}`).then((r) => r.json()));

O fetcher recebe o valor da fonte e { value, refetching }. Pode devolver uma promise ou um valor comum. Enquanto a fonte for false, null ou undefined, nada é buscado.

Lendo um recurso

tsx
post();          // o valor, undefined até a primeira carga
post.state;      // 'unresolved' | 'pending' | 'ready' | 'refreshing' | 'errored'
post.loading;    // true enquanto há uma requisição em andamento
post.error;      // o último erro
post.latest;     // o último valor bem-sucedido (nunca lança erro)

Todos são reativos. Se a última carga falhou, chamar post() lança o erro, e o ErrorBoundary mais próximo mostra o fallback dele.

Quando a fonte muda antes de uma requisição terminar, a resposta atrasada é ignorada. Quando o dono é descartado, os resultados pendentes são descartados também.

mutate e refetch

tsx
const [todos, { mutate, refetch }] = createResource(fetchTodos);

async function add(title: string) {
  mutate([...(todos() ?? []), { title, done: false }]); // otimista
  await api.add(title);
  refetch();
}

Suspense

O Suspense mostra o fallback enquanto um recurso lido dentro dele carrega pela primeira vez.

tsx
function Profile() {
  const [user] = createResource(fetchUser);
  return <h2>{() => user()?.name}</h2>;
}

<Suspense fallback={<p>Carregando…</p>}>
  {() => <Profile />}
</Suspense>
  • Ele espera todos os recursos lidos dentro dele.
  • Recargas não trazem o fallback de volta: o conteúdo atual fica, e você pode mostrar user.loading como um indicador discreto.
  • O conteúdo é criado uma vez e continua vivo enquanto escondido, então o estado dele sobrevive.
  • Passe os filhos como função para que os recursos sejam criados dentro do limite.

Erros

tsx
<ErrorBoundary fallback={(error, reset) => <button onClick={reset}>{error.message}: tentar de novo</button>}>
  {() => (
    <Suspense fallback={<Spinner />}>
      {() => <Profile />}
    </Suspense>
  )}
</ErrorBoundary>

Teste

A demonstração da página inicial usa exatamente isso: um recurso com fonte, Suspense, ErrorBoundary, refetch e uma falha simulada.

No servidor

O renderToString não espera: recursos pendentes renderizam o fallback do Suspense. O renderToStringAsync espera os dados, coloca-os no HTML e devolve um script que entrega os valores ao hydrate(), então o cliente começa com eles em vez de buscar de novo. Veja Renderização no servidor.

Os recursos são associados entre servidor e cliente pela ordem de criação. Se essa ordem puder variar, dê a eles um nome estável:

ts
createResource(fetchUser, { name: 'current-user' });