Saltar al contenido principal

Renderizar datos asíncronos

Haz que tus componentes sean reutilizables vinculando los datos donde los usas con el useSuspense() de una sola línea, que garantiza los datos como await.

import { useSuspense } from '@data-client/react';
import PostItem from './PostItem';
import { PostResource } from './Resources';

export default function PostList({ setRoute }) {
  const posts = useSuspense(PostResource.getList);
  return (
    <div>
      {posts.map(post => (
        <PostItem key={post.pk()} post={post} setRoute={setRoute} />
      ))}
    </div>
  );
}
Resultado
Store▶
Endpoints usados en muchos contextosEndpoints usados en muchos contextos

No hagas prop drilling. En su lugar, usa useSuspense() en los componentes que renderizan los datos. Esto se conoce como colocación de datos (data co-location).

No ocultes los hooks de vinculación de datos dentro de hooks personalizados. En su lugar, coloca las transformaciones de datos estrechamente acopladas en un Query: la lógica de datos pertenece al modelo de datos, donde permanece visible, es reutilizable y puede cambiar de forma independiente de la vista.

En lugar de escribir complejas funciones de actualización o cascadas de invalidaciones, Reactive Data Client actualiza automáticamente los componentes vinculados de inmediato cuando los datos cambian. Esto se conoce como programación reactiva.

Carga y error​

Es posible que hayas notado que el tipo de retorno indica que el valor siempre está presente. useSuspense() funciona de forma muy parecida a await. Esto nos permite separar el manejo de errores y de carga del uso de los datos.

Async Boundaries​

En su lugar, colocamos <AsyncBoundary /> para manejar las condiciones de carga y error en o por encima de los límites de navegación, como páginas, rutas o modales.

Dashboard.tsx
import { AsyncBoundary } from '@data-client/react';
import { Outlet } from 'react-router';

export default function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
<section>
<AsyncBoundary>
<Outlet />
</AsyncBoundary>
</section>
</div>
);
}

El useTransition de React 18 y los enrutadores o la navegación basados en renderizado del lado del servidor hacen que nunca vuelvas a ver un fallback de carga. En React 16 y 17 los fallbacks pueden centralizarse para eliminar indicadores de carga redundantes y mantener los componentes reutilizables.

<AsyncBoundary /> también permite que el renderizado del lado del servidor transmita HTML de forma incremental, lo que reduce considerablemente el TTFB. La hidratación automática del store de Reactive Data Client SSR significa interactividad inmediata para el usuario con cero fetches del lado del cliente en la primera carga.

Tanto el fallback de error como el fallback de carga de AsyncBoundary se pueden personalizar.

Con estado​

Puede haber casos en los que siga siendo útil un enfoque con estado para los fallbacks al usar React 16 y 17. Para estos casos, o para la compatibilidad con algunas bibliotecas de componentes, se proporciona useDLE() - [D]ata [L]oading [E]rror (datos, carga, error).

import React from 'react';
import { useDLE } from '@data-client/react';
import { ProfileResource } from './ProfileResource';

function ProfileList(): React.JSX.Element {
  const { data, loading, error } = useDLE(ProfileResource.getList);
  if (error) return <div>Error {`${error.status}`}</div>;
  if (loading || !data) return <Loading />;
  return (
    <div>
      {data.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 />);
Resultado
Store▶

Dado que useDLE no usa useSuspense, no podrás orquestar fácilmente de forma central el código de carga y de error . Además, las funcionalidades de React 18 como useTransition y el SSR con transmisión incremental no funcionarán con los componentes que lo usen.

Condicional​

Dependencias condicionales

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

Suscripciones​

Cuando es probable que los datos cambien por factores externos, useSubscription() garantiza actualizaciones continuas mientras un componente está montado. useLive() llama tanto a useSubscription() como a useSuspense(), lo que hace muy fácil usar datos actualizados.

import { useLive } from '@data-client/react';
import NumberFlow from '@number-flow/react';
import { getTicker } from './Ticker';

function AssetPrice({ productId }: Props) {
  const ticker = useLive(getTicker, { productId });
  return (
    <center>
      {productId}{' '}
      <NumberFlow
        value={ticker.price}
        format={{ style: 'currency', currency: 'USD' }}
      />
    </center>
  );
}
interface Props {
  productId: string;
}
render(<AssetPrice productId="BTC-USD" />);
Resultado
Store▶

Las suscripciones son orquestadas por los Managers. De fábrica, se pueden usar suscripciones basadas en sondeo (polling) agregando pollFrequency a un Endpoint o Resource. Para protocolos de red basados en push, como SSE y websockets, consulta el ejemplo de stream manager.

export const getTicker = new RestEndpoint({
urlPrefix: 'https://api.exchange.coinbase.com',
path: '/products/:productId/ticker',
schema: Ticker,
pollFrequency: 2000,
});