# Render as you Fetch

A core design feature of Reactive Data Client is decoupling actual data retrieval from data
usage. This means hooks that want to ensure data availability like [useFetch()](https://dataclient.io/docs/api/useFetch.md)
or [useSuspense()](https://dataclient.io/docs/api/useSuspense.md) actually only dispatch the request to fetch. [NetworkManager](https://dataclient.io/docs/api/NetworkManager.md)
then uses its global awareness to determine whether to fetch. This means, for instance, that
duplicate requests for data can be deduped into one fetch, with one promise to resolve.

Another interesting implication is that fetches started imperatively via [Controller.fetchIfStale()](https://dataclient.io/docs/api/Controller.md#fetchIfStale) and [Controller.fetch()](https://dataclient.io/docs/api/Controller.md#fetch)
won't result in redundant fetches. This is known as 'fetch as you render,' and often results
in an improved user experience.

These are some scenarios where this pattern is especially useful:

- Server Side Rendering
- Loading data in parallel with code
- [Concurrent Mode](https://react.dev/blog/2022/03/29/react-v18#what-is-concurrent-react)
  - [useTransition()](https://react.dev/reference/react/useTransition)

Fetch-as-you-render can be adopted incrementally. Components using data can [useSuspense()](https://dataclient.io/docs/api/useSuspense.md)
and be assured they will get their data when it's ready. And when render-as-you-fetch optimizations
are added later - _those components don't need to change_. This makes data usage _tightly coupled_,
and fetch optimization _loosely coupled_.

## Routes that preload

In most cases the best time to pre-fetch data is at the routing layer. Doing this
makes incorporating all of the above capabilities quite easy.

Use [Controller.fetchIfStale](https://dataclient.io/docs/api/Controller.md#fetchIfStale) in the route event handler (before startTransition)

```ts
import { Controller } from '@data-client/react';
import { lazy, Route } from '@anansi/router';
import { getImage } from '@data-client/img';

import { lazyPage } from './lazyPage';
import { PostResource } from '@/resources/Post';
import { UserResource } from '@/resources/User';

export const routes: Route<Controller>[] = [
  {
    name: 'UserDetail',
    component: lazyPage('UserDetail'),
    resolveData: async (controller: Controller, match: { id: string }) => {
      if (match) {
        const fakeUser = UserResource.fromJS({
          id: Number.parseInt(match.id, 10),
        });
        // don't block on posts but start fetching
        controller.fetchIfStale(PostResource.getList, { userId: match.id });
        await Promise.all([
          controller.fetchIfStale(UserResource.get, match),
          controller.fetchIfStale(getImage, {
            src: fakeUser.profileImage,
          }),
          controller.fetchIfStale(getImage, {
            src: fakeUser.coverImage,
          }),
          controller.fetchIfStale(getImage, {
            src: fakeUser.coverImageFallback,
          }),
        ]);
      }
    },
  },
];
```

### Components using data

[UserDetail page](https://stackblitz.com/github/ntucker/anansi/tree/master/examples/concurrent?file=src%2Fpages%2FUserDetail%2Findex.tsx)

```tsx
import { useSuspense } from '@data-client/react';
import { Img } from '@data-client/img';
import { Card, Avatar } from 'antd';

import { UserResource } from 'resources/Discuss';
import Boundary from 'Boundary';
import PostList from 'pages/Posts';

export type Props = { id: string };
const { Meta } = Card;

export default function UserDetail({ id }: Props) {
  const user = useSuspense(UserResource.get, { id });
  return (
    <>
      <Card cover={<Img src={user.coverImage} />}>
        <Meta
          avatar={<Img component={Avatar} src={user.profileImage} size={64} />}
          title={user.name}
          description={
            <>
              <div>{user.website}</div>
              <div>{user.company.catchPhrase}</div>
            </>
          }
        />
      </Card>
      <Boundary fallback={<CardLoading />}>
        <PostList userId={user.pk()} />
      </Boundary>
    </>
  );
}
export function CardLoading() {
  return <Card style={{ marginTop: 16 }} loading={true} />;
}
```
