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

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.
- React Router
- NextJS
- Expo
- Antd Modal
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>
);
}
import { AsyncBoundary } from '@data-client/react';
export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div>
<h1>Dashboard</h1>
<section>
<AsyncBoundary>{children}</AsyncBoundary>
</section>
</div>
);
}
import { AsyncBoundary } from '@data-client/react';
import { Slot } from 'expo-router';
import { Image, StyleSheet } from 'react-native';
import ParallaxScrollView from '@/components/ParallaxScrollView';
export default function DashboardLayout() {
return (
<ParallaxScrollView
headerBackgroundColor={{ light: '#A1CEDC', dark: '#1D3D47' }}
headerImage={
<Image
source={require('@/assets/images/my-logo.png')}
style={styles.logo}
/>
}
>
<AsyncBoundary>
<Slot />
</AsyncBoundary>
</ParallaxScrollView>
);
}
const styles = StyleSheet.create({
logo: { height: 178, width: 290 },
});
import { AsyncBoundary } from '@data-client/react';
import { useState } from 'react';
import { Button, Modal } from 'antd';
import MyModalBody from './MyModalBody';
export default function ModalOpen() {
const [isModalOpen, setIsModalOpen] = useState(false);
const showModal = () => setIsModalOpen(true);
const handleOk = () => setIsModalOpen(false);
const handleCancel = () => setIsModalOpen(false);
return (
<>
<Button type="primary" onClick={showModal}>
Open Modal
</Button>
<Modal title="Basic Modal" open={isModalOpen} onOk={handleOk} onCancel={handleCancel}>
<AsyncBoundary>
<MyModalBody />
</AsyncBoundary>
</Modal>
</>
);
}
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 />);
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
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" />);
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,
});