Rendering Asynchronous Data
Make your components reusable by binding the data where you use it with the one-line useSuspense(), which guarantees data with await.
<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>

Do not prop drill. Instead, useSuspense() 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 — 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. 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 with await. This enables us to make error/loading disjoint from data usage.
Async Boundaries
Instead we place Vue's built-in <Suspense /> along with onErrorCaptured() to handling loading and error conditions at or above navigational boundaries like pages, routes, or modals.
<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 />,
and the error fallback by rendering what you choose from 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() - [D]ata [L]oading [E]rror - is provided.
<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 does not useSuspense, you won't be able to easily centrally orchestrate loading and error code.
Conditional
Use null as the second argument to any Data Client hook means "do nothing."
// 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() ensures continual updates while a component is mounted. useLive() calls both useSubscription() and useSuspense(), making it quite easy to use fresh data.
<script setup lang="ts"> import { useLive } from '@data-client/vue'; import { getTicker } from './Ticker'; const props = defineProps<{ productId: string }>(); const ticker = await useLive(getTicker, { productId: props.productId }); </script> <template> <center> {{ productId }} <NumberFlow :value="ticker.price" :format="{ style: 'currency', currency: 'USD' }" /> </center> </template>
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,
});