Skip to main content

Rendering Asynchronous Data

Make your components reusable by binding the data where you use it with the one-line useSuspense(), which guarantees data like 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>
  );
}
🔴 Live Preview
Store
Endpoints used in many contextsEndpoints used in many contexts

Do not prop drill. Instead, useSuspense() in the components that render the data from it. This is known as data co-location.

Instead of writing complex update functions or invalidations cascades, Reactive Data Client automatically updates bound components immediately upon data change. This is known as reactive programming.

Loading and Error

You might have noticed the return type shows the value is always there. useSuspense() operates very much like await. This enables us to make error/loading disjoint from data usage.

Async Boundaries

Instead we place <AsyncBoundary /> to handling loading and error conditions at or above navigational boundaries like pages, routes, or modals.

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

React 18's useTransition and Server Side Rendering powered routers or navigation means never seeing a loading fallback again. In React 16 and 17 fallbacks can be centralized to eliminate redundant loading indicators while keeping components reusable.

<AsyncBoundary /> also allows Server Side Rendering to incrementally stream HTML, greatly reducing TTFB. Reactive Data Client SSR's automatic store hydration means immediate user interactivity with zero client-side fetches on first load.

AsyncBoundary's error fallback and loading fallback can both be customized.

Stateful

You may find cases where it's still useful to use a stateful approach to fallbacks when using React 16 and 17. For these cases, or compatibility with some component libraries, useDLE() - [D]ata [L]oading [E]rror - is provided.

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

function ProfileList(): 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 />);
🔴 Live Preview
Store

Since useDLE does not useSuspense, you won't be able to easily centrally orchestrate loading and error code. Additionally, React 18 features like useTransition, and incrementally streaming SSR won't work with components that use it.

Conditional

Conditional Dependencies

Use null as the second argument to any Data Client hook means "do nothing."

// todo could be undefined if id is undefined
const todo = useSuspense(TodoResource.get, id ? { id } : null);

Subscriptions

When data is likely to change due to external factor; useSubscription() ensures continual updates while a component is mounted. useLive() calls both useSubscription() and useSuspense(), making it quite easy to use fresh data.

import { useLive } from '@data-client/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" />);
🔴 Live Preview
Store

Subscriptions are orchestrated by Managers. Out of the box, polling based subscriptions can be used by adding pollFrequency to an Endpoint or Resource. For pushed based networking protocols like SSE and websockets, see the example stream manager.

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