# Rendering Asynchronous Data

Make your components reusable by binding the data where you **use** it with the one-line [useSuspense()](https://dataclient.io/vue/api/useSuspense.md),
which guarantees data with [await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await).

```ts title="Resources"
import { Entity, resource } from '@data-client/rest';

export class User extends Entity {
  id = 0;
  name = '';
  username = '';
  email = '';
  phone = '';
  website = '';

  get profileImage() {
    return `https://i.pravatar.cc/64?img=${this.id + 4}`;
  }

  static key = 'User';
}
export const UserResource = resource({
  urlPrefix: 'https://jsonplaceholder.typicode.com',
  path: '/users/:id',
  schema: User,
});

export class Post extends Entity {
  id = 0;
  author = User.fromJS();
  title = '';
  body = '';

  static key = 'Post';

  static schema = {
    author: User,
  };
}
export const PostResource = resource({
  path: '/posts/:id',
  schema: Post,
  paginationField: 'page',
});
```

```html title="PostDetail.vue" {7}
<script setup lang="ts">
  import { useSuspense } from '@data-client/vue';
  import { PostResource } from './Resources';

  const props = defineProps<{ id: string }>();
  const emit = defineEmits<{ setRoute: [route: string] }>();
  const post = await useSuspense(PostResource.get, () => ({ id: props.id }));
</script>

<template>
  <div>
    <header>
      <div class="listItem spaced">
        <div class="author">
          <Avatar :src="post.author.profileImage" />
          <small>{{ post.author.name }}</small>
        </div>
        <h4>{{ post.title }}</h4>
      </div>
    </header>
    <p>{{ post.body }}</p>
    <a href="#" @click.prevent="emit('setRoute', 'list')">« Back</a>
  </div>
</template>
```

```html title="PostItem.vue"
<script setup lang="ts">
  import { type Post } from './Resources';

  defineProps<{ post: Post }>();
  const emit = defineEmits<{ setRoute: [route: string] }>();
</script>

<template>
  <div class="listItem spaced">
    <Avatar :src="post.author.profileImage" />
    <div>
      <h4>
        <a href="#" @click.prevent="emit('setRoute', `detail/${post.id}`)">
          {{ post.title }}
        </a>
      </h4>
      <small>by {{ post.author.name }}</small>
    </div>
  </div>
</template>
```

```html title="PostList.vue" {7}
<script setup lang="ts">
  import { useSuspense } from '@data-client/vue';
  import PostItem from './PostItem.vue';
  import { PostResource } from './Resources';

  const emit = defineEmits<{ setRoute: [route: string] }>();
  const posts = await useSuspense(PostResource.getList);
</script>

<template>
  <div>
    <PostItem
      v-for="post in posts"
      :key="post.pk()"
      :post="post"
      @setRoute="emit('setRoute', $event)"
    />
  </div>
</template>
```

```html title="LoadMore.vue"
<script setup lang="ts">
  import { computed } from 'vue';
  import { useController, useLoading, useQuery } from '@data-client/vue';
  import { PostResource } from './Resources';

  const ctrl = useController();
  const posts = useQuery(PostResource.getList.schema);
  const [nextPage, isPending] = useLoading(() =>
    ctrl.fetch(PostResource.getList.getPage, { page: 2 }),
  );
  const canLoadMore = computed(
    () => !!posts.value && posts.value.length % 3 === 0,
  );
</script>

<template>
  <div v-if="canLoadMore" style="text-align: center">
    <button @click="nextPage">
      {{ isPending ? '...' : 'Load more' }}
    </button>
  </div>
</template>
```

```html title="Navigation.vue"
<script setup lang="ts">
  import { ref, computed } from 'vue';
  import PostList from './PostList.vue';
  import PostDetail from './PostDetail.vue';
  import LoadMore from './LoadMore.vue';

  const route = ref('list');
  const detailId = computed(() =>
    route.value.startsWith('detail')
      ? route.value.split('/')[1]
      : undefined,
  );
</script>

<template>
  <PostDetail v-if="detailId" :id="detailId" @setRoute="route = $event" />
  <template v-else>
    <PostList @setRoute="route = $event" />
    <LoadMore />
  </template>
</template>
```

[](https://react.dev/learn/passing-data-deeply-with-context)

Do not [prop drill](https://react.dev/learn/passing-data-deeply-with-context#the-problem-with-passing-props). Instead, [useSuspense()](https://dataclient.io/vue/api/useSuspense.md) in the components that render the data from it. This is
known as _data co-location_.

Do not hide data binding hooks inside custom hooks. Instead, put tightly coupled data transformations
in [Query](https://dataclient.io/rest/api/Query.md) — data logic belongs with the data model, where it stays visible, reusable,
and free to change independently of the view.

Instead of writing complex update functions or invalidations cascades, Reactive Data Client automatically updates
bound components immediately upon [data change](https://dataclient.io/vue/getting-started/mutations.md). This is known as _reactive programming_.

## Loading and Error {#async-fallbacks}

You might have noticed the return type shows the value is always there. [useSuspense()](https://dataclient.io/vue/api/useSuspense.md) operates very much
with [await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await). This enables
us to make error/loading disjoint from data usage.

### Async Boundaries {#boundaries}

Instead we place Vue's built-in [\<Suspense />](https://vuejs.org/guide/built-ins/suspense.html) along with [onErrorCaptured()](https://vuejs.org/api/composition-api-lifecycle.html#onerrorcaptured) to handling loading and error conditions at or above navigational boundaries like **pages,
routes, or [modals](https://www.appcues.com/blog/modal-dialog-windows)**.

```html title="Dashboard.vue" {13-20}
<script setup lang="ts">
  import { onErrorCaptured, ref } from 'vue';

  const error = ref<Error | null>(null);
  onErrorCaptured(err => {
    error.value = err;
    return false;
  });
</script>

<template>
  <div>
    <h1>Dashboard</h1>
    <section>
      <div v-if="error">Error: {{ error.message }}</div>
      <Suspense v-else>
        <template #default>
          <RouterView />
        </template>
        <template #fallback>
          <Loading />
        </template>
      </Suspense>
    </section>
  </div>
</template>
```

Centralizing fallbacks this way eliminates redundant loading indicators while keeping components reusable.
The loading fallback is customized with the `#fallback` slot of [\<Suspense />](https://vuejs.org/guide/built-ins/suspense.html#loading-state),
and the error fallback by rendering what you choose from [onErrorCaptured()](https://vuejs.org/api/composition-api-lifecycle.html#onerrorcaptured).

### Stateful

You may find cases where it's still useful to use a stateful approach to fallbacks.
For these cases, or compatibility with some component libraries, [useDLE()](https://dataclient.io/vue/api/useDLE.md) - \[D]ata \[L]oading \[E]rror - is provided.

```typescript title="ProfileResource"
import { Entity, resource } from '@data-client/rest';

export class Profile extends Entity {
  id: number | undefined = undefined;
  avatar = '';
  fullName = '';
  bio = '';

  static key = 'Profile';
}

export const ProfileResource = resource({
  path: '/profiles/:id',
  schema: Profile,
});
```

```html title="ProfileList.vue" {5}
<script setup lang="ts">
  import { useDLE } from '@data-client/vue';
  import { ProfileResource } from './ProfileResource';

  const { data, loading, error } = useDLE(ProfileResource.getList);
</script>

<template>
  <div v-if="error">Error {{ error.status }}</div>
  <Loading v-else-if="loading || !data" />
  <div v-else>
    <div class="listItem" v-for="profile in data" :key="profile.pk()">
      <Avatar :src="profile.avatar" />
      <div>
        <h4>{{ profile.fullName }}</h4>
        <p>{{ profile.bio }}</p>
      </div>
    </div>
  </div>
</template>
```

Since [useDLE](https://dataclient.io/vue/api/useDLE.md) does not [useSuspense](https://dataclient.io/vue/api/useSuspense.md), you won't be able to easily centrally
orchestrate loading and error code.

## Conditional

> **Tip: Conditional Dependencies**
>
> Use `null` as the second argument to any Data Client hook means "do nothing."
>
> ```typescript
> // todo could be undefined if id is undefined
> const todo = await useSuspense(
>   TodoResource.get,
>   computed(() => (id.value ? { id: id.value } : null)),
> );
> ```

## Subscriptions

When data is likely to change due to external factor; [useSubscription()](https://dataclient.io/vue/api/useSubscription.md)
ensures continual updates while a component is mounted. [useLive()](https://dataclient.io/vue/api/useLive.md) calls both
[useSubscription()](https://dataclient.io/vue/api/useSubscription.md) and [useSuspense()](https://dataclient.io/vue/api/useSuspense.md), making it quite
easy to use fresh data.

```typescript title="Ticker" {33}
import { Entity, RestEndpoint } from '@data-client/rest';
import { Temporal } from 'temporal-polyfill';

export class Ticker extends Entity {
  product_id = '';
  trade_id = 0;
  price = 0;
  size = '0';
  time = Temporal.Instant.fromEpochMilliseconds(0);
  bid = '0';
  ask = '0';
  volume = '';

  pk(): string {
    return this.product_id;
  }
  static key = 'Ticker';

  static schema = {
    price: Number,
    time: Temporal.Instant.from,
  };
}

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

```html title="AssetPrice.vue" {7}
<script setup lang="ts">
  import { useLive } from '@data-client/vue';
  import { getTicker } from './Ticker';
  import NumberFlow from '@number-flow/vue';

  const props = defineProps<{ productId: string }>();
  const ticker = await useLive(getTicker, () => ({ productId: props.productId }));
</script>

<template>
  <div style="text-align: center">
    {{ productId }}
    <NumberFlow
      :value="ticker.price"
      :format="{ style: 'currency', currency: 'USD' }"
    />
  </div>
</template>
```

Subscriptions are orchestrated by [Managers](https://dataclient.io/vue/api/Manager.md). Out of the box,
polling based subscriptions can be used by adding [pollFrequency](https://dataclient.io/rest/api/Endpoint.md#pollfrequency) to an Endpoint or Resource.
For pushed based networking protocols like SSE and websockets, see the [example stream manager](https://dataclient.io/vue/concepts/managers.md#data-stream).

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