useSuspense()
High performance async data rendering without overfetching.
useSuspense() es como await para componentes de React. Esto significa que el resto del componente solo se ejecuta después de que los datos se hayan cargado, lo que evita la complejidad de gestionar las condiciones de carga y de error. En su lugar, el manejo de los fallbacks se
centraliza en un único AsyncBoundary.
useSuspense() reacciona a las mutaciones de los datos y vuelve a renderizar solo cuando es necesario.
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 />);
Comportamiento
La política de caché es Stale-While-Revalidate de forma predeterminada, pero también es configurable.
| Estado de caducidad | Fetch | Suspende | Error | Condiciones |
|---|---|---|---|---|
| Inválido | sí1 | sí | no | no está en el store, eliminación, invalidación, invalidIfStale |
| Obsoleto | sí1 | no | no | (primer render, cambio de argumentos) & caducidad < ahora |
| Válido | no | no | quizá2 | finalización del fetch |
| no | no | no | null usado como segundo argumento |
- Los fetches idénticos se deduplican automáticamente
- Los errores duros deben ser capturados por Error Boundaries
Al usar React Navigation, useSuspense() lanzará fetches al recibir el foco si los datos se consideran obsoletos.
Usar null como segundo argumento de cualquier hook de Data Client significa "no hacer 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>;
Ejemplos
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 />);
Paginación
La paginación reactiva se logra con schemas mutables
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 />);
Secuencial
Cuando los parámetros del fetch dependen de datos de otro recurso.
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 evitará enlazar y obtener los datos
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; }
Datos incrustados
Cuando las entidades se almacenan en estructuras anidadas, esa estructura se conserva.
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> ); }
Renderizado del lado del servidor
El renderizado del lado del servidor permite enviar el HTML en streaming de forma incremental, lo que reduce enormemente el TTFB. La hidratación automática del store del SSR de Reactive Data Client significa interactividad inmediata para el usuario con cero fetches del lado del cliente en la primera carga.
Explora el ejemplo nextjs
El uso en los componentes es idéntico, lo que significa que puedes compartir fácilmente componentes entre aplicaciones con y sin SSR, así como migrar a SSR sin necesidad de cambiar el código de data-client.
Modo concurrente
En React 18, navegar con startTransition permite que los AsyncBoundaries sigan mostrando la pantalla anterior mientras se cargan los nuevos datos. Combinado con el renderizado del lado del servidor con streaming, elimina la necesidad de mostrar indicadores de carga molestos y mejora la experiencia de usuario.
Haz clic en uno de los nombres para navegar a sus tareas. Aquí los estados de carga largos se indican con una barra de carga menos intrusiva, como la que usan YouTube y Robinhood.
Explora el ejemplo todo-app
Si necesitas ayuda para añadir esto a tu propio router personalizado, consulta la guía oficial de React