useQuery()
Data rendering without the fetch.
Access any Queryable Schema's store value; like Entity, All, Collection, Query,
Union, and Scalar. Lazy fields also work via their .query accessor.
If the value does not exist, returns undefined.
useQuery() is reactive to data mutations; rerendering only when necessary. Returns undefined
when data is Invalid.
Usage
<script setup lang="ts"> import { Query } from '@data-client/rest'; import { useQuery } from '@data-client/vue'; import NumberFlow from '@number-flow/vue'; import { PostResource } from './PostResource'; const queryTotalVotes = new Query( PostResource.getList.schema, posts => posts.reduce((total, post) => total + post.votes, 0), ); const props = defineProps<{ userId: number }>(); const totalVotes = useQuery(queryTotalVotes, () => ({ userId: props.userId })); </script> <template> <span v-if="totalVotes !== undefined" class="meta"> <NumberFlow :value="totalVotes" /> votes </span> </template>
See truthiness narrowing for more information about type handling
Types
function useQuery<S extends Queryable>(
schema: S,
...args: MaybeRefsOrGetters<SchemaArgs<S>>
): ComputedRef<DenormalizeNullable<S> | undefined>;
Arguments can be plain values, refs (including computed), or getter
functions like () => ({ id: props.id }). A plain object like { id: props.id } is read once and won't
follow prop or route changes, so use a getter or computed when an argument can change.
The result updates when the arguments change.
Queryable
Queryable schemas require an queryKey() method that returns something. These include
Entity, All, Collection, Query,
Union, and Scalar. Lazy fields produce a Queryable via their .query accessor.
interface Queryable {
queryKey(
args: readonly any[],
queryKey: (...args: any) => any,
getEntity: GetEntity,
getIndex: GetIndex,
// Must be non-void
): {};
}
Examples
Sorting & Filtering
Query sorts and filters what is already in the store, so picking another column re-sorts the table without fetching again.
<script lang="ts"> import { All, Query } from '@data-client/rest'; import { type Sort, User } from './resources/User'; interface Args extends Sort { /** admins only (true), members only (false), or everyone */ isAdmin: boolean | undefined; } const sortedUsers = new Query( new All(User), (entries, { sortBy, desc, isAdmin }: Args) => { const users = entries .filter(user => isAdmin === undefined || user.isAdmin === isAdmin) .sort((a, b) => sortBy === 'name' ? a.name.localeCompare(b.name) : a[sortBy] - b[sortBy], ); return desc ? users.reverse() : users; }, ); </script> <script setup lang="ts"> import { useQuery, useSuspense } from '@data-client/vue'; import { UserResource } from './resources/User'; import UsersTableView from './UsersTableView.vue'; const props = defineProps<{ sort: Sort; isAdmin: boolean | undefined }>(); defineEmits<{ sort: [sort: Sort] }>(); await useSuspense(UserResource.getList); const users = useQuery(sortedUsers, () => ({ ...props.sort, isAdmin: props.isAdmin, })); </script> <template> <UsersTableView :users="users ?? []" :sort="sort" @sort="$emit('sort', $event)" /> </template>
Lazy relationships
Lazy fields keep raw IDs during parent denormalization. Use .query with useQuery to resolve them on demand,
isolating re-renders to only the components that need the related data.
<script setup lang="ts"> import { computed } from 'vue'; import { useQuery } from '@data-client/vue'; import { Department } from './Resources'; const props = defineProps<{ dept: Department }>(); const buildings = useQuery( Department.schema.buildings.query, computed(() => props.dept.buildings), ); </script> <template> <span v-if="buildings">{{ buildings.map(b => b.name).join(', ') }}</span> </template>
<script setup lang="ts"> import { All } from '@data-client/rest'; import { useQuery, useFetch } from '@data-client/vue'; import { DepartmentResource, Department } from './Resources'; import BuildingList from './BuildingList.vue'; useFetch(DepartmentResource.getList); const departments = useQuery(new All(Department)); </script> <template> <Loading v-if="!departments" /> <div v-else> <div v-for="dept in departments" :key="dept.pk()"> <strong>{{ dept.name }}</strong>: <BuildingList :dept="dept" /> </div> </div> </template>