useSuspense()
High performance async data rendering without overfetching.
useSuspense() é como o await para componentes React. Isso significa que o restante do componente só é executado depois que os dados foram carregados, evitando a complexidade de tratar condições de carregamento e de erro. Em vez disso, o tratamento de fallback é
centralizado em um único AsyncBoundary.
useSuspense() reage às mutações de dados, renderizando novamente somente quando necessário.
Uso
- Rest
- Promise
import { useSuspense } from '@data-client/react'; import { ProfileResource } from './ProfileResource'; function ProfileDetail() { const profile = useSuspense(ProfileResource.get, { id: 1 }); return ( <div className="listItem"> <Avatar src={profile.avatar} /> <div> <h4>{profile.fullName}</h4> <p>{profile.bio}</p> </div> </div> ); } render(<ProfileDetail />);
import { useSuspense } from '@data-client/react'; import { getProfile } from './Profile'; function ProfileDetail() { const profile = useSuspense(getProfile, 1); return ( <div className="listItem"> <Avatar src={profile.avatar} /> <div> <h4>{profile.fullName}</h4> <p>{profile.bio}</p> </div> </div> ); } render(<ProfileDetail />);
Comportamento
A política de cache é Stale-While-Revalidate por padrão, mas também configurável.
| Status de expiração | Fetch | Suspend | Error | Condições |
|---|---|---|---|---|
| Inválido | sim1 | sim | não | não está na store, exclusão, invalidação, invalidIfStale |
| Desatualizado | sim1 | não | não | (primeira renderização, mudança de args) & expiração < agora |
| Válido | não | não | talvez2 | conclusão do fetch |
| não | não | não | null usado como segundo argumento |
- Fetches idênticos são automaticamente deduplicados
- Erros hard devem ser capturados por Error Boundaries
Ao usar o React Navigation, useSuspense() dispara fetches ao receber foco se os dados forem considerados desatualizados.
Usar null como segundo argumento de qualquer hook do Data Client significa "não fazer nada."
// todo could be undefined if id is undefined
const todo = useSuspense(TodoResource.get, id ? { id } : null);
Tipos
- Type
- With Generics
function useSuspense(
endpoint: ReadEndpoint,
...args: Parameters<typeof endpoint> | [null]
): Denormalize<typeof endpoint.schema>;
function useSuspense<
E extends EndpointInterface<
FetchFunction,
Schema | undefined,
undefined
>,
Args extends readonly [...Parameters<E>] | readonly [null],
>(
endpoint: E,
...args: Args
): E['schema'] extends Exclude<Schema, null>
? Denormalize<E['schema']>
: ReturnType<E>;
Exemplos
Lista
import { useSuspense } from '@data-client/react'; import { ProfileResource } from './ProfileResource'; function ProfileList() { const profiles = useSuspense(ProfileResource.getList); return ( <div> {profiles.map(profile => ( <div className="listItem" key={profile.pk()}> <Avatar src={profile.avatar} /> <div> <h4>{profile.fullName}</h4> <p>{profile.bio}</p> </div> </div> ))} </div> ); } render(<ProfileList />);
Paginação
A paginação reativa é obtida com schemas mutáveis
import { useSuspense } from '@data-client/react'; import PostItem from './PostItem'; import LoadMore from './LoadMore'; import { PostResource } from './Post'; export default function PostList() { const { posts, cursor } = useSuspense(PostResource.getList); return ( <div> {posts.map(post => ( <PostItem key={post.pk()} post={post} /> ))} {cursor ? <LoadMore cursor={cursor} /> : null} </div> ); } render(<PostList />);
Sequencial
Quando os parâmetros do fetch dependem de dados de outro resource.
import { useSuspense } from '@data-client/react';
import { PostResource, UserResource } from './resources';
function PostWithAuthor({ id }: { id: string }) {
const post = useSuspense(PostResource.get, { id });
const author = useSuspense(UserResource.get, {
id: post.userId,
});
}
Condicional
null evita vincular e buscar os dados
import { useSuspense } from '@data-client/react'; import { PostResource, UserResource } from './Resources'; export default function PostWithAuthor({ id }: { id: string }) { const post = useSuspense(PostResource.get, { id }); const author = useSuspense( UserResource.get, post.userId ? { id: post.userId, } : null, ); // author as User | undefined if (!author) return; }
Dados incorporados
Quando as entidades são armazenadas em estruturas aninhadas, essa estrutura é mantida.
import { Entity, RestEndpoint, Collection } from '@data-client/rest'; export class PaginatedPost extends Entity { id = ''; title = ''; content = ''; static key = 'PaginatedPost'; } export const getPosts = new RestEndpoint({ path: '/post', searchParams: { page: '' }, schema: { posts: new Collection([PaginatedPost]), nextPage: '', lastPage: '', }, });
import { useSuspense } from '@data-client/react'; import { getPosts } from './api/Post'; export default function ArticleList({ page }: { page: string }) { const { posts, nextPage, lastPage, } = useSuspense(getPosts, { page }); return ( <div> {posts.map(post => ( <div key={post.pk()}>{post.title}</div> ))} </div> ); }
Renderização no servidor
A renderização no servidor (Server Side Rendering) transmite o HTML de forma incremental, reduzindo bastante o TTFB. A hidratação automática da store do SSR do Reactive Data Client significa interatividade imediata para o usuário, com zero fetches no cliente no primeiro carregamento.
Explore o exemplo nextjs
O uso nos componentes é idêntico, o que significa que você pode compartilhar componentes com facilidade entre aplicações com e sem SSR, além de migrar para SSR sem precisar alterar o código do data-client.
Modo concorrente
No React 18, navegar com startTransition permite que os AsyncBoundaries continuem exibindo a tela anterior enquanto os novos dados carregam. Combinado com a renderização no servidor com streaming, isso elimina a necessidade de exibir indicadores de carregamento irritantes, melhorando a experiência do usuário.
Clique em um dos nomes para navegar até as tarefas dessa pessoa. Aqui, estados de carregamento longos são indicados pela barra de carregamento, menos intrusiva, como a usada pelo YouTube e pelo Robinhood.
Explore o exemplo todo-app
Se precisar de ajuda para adicionar isso ao seu próprio roteador personalizado, consulte o guia oficial do React