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
import { Query } from '@data-client/rest'; import { useQuery } from '@data-client/react'; import NumberFlow from '@number-flow/react'; import { PostResource } from './PostResource'; const queryTotalVotes = new Query( PostResource.getList.schema, posts => posts.reduce((total, post) => total + post.votes, 0), ); export default function TotalVotes({ userId }: Props) { const totalVotes = useQuery(queryTotalVotes, { userId }); if (totalVotes === undefined) return null; return ( <span className="meta"> <NumberFlow value={totalVotes} /> votes </span> ); } interface Props { userId: number; }
See truthiness narrowing for more information about type handling
Types
- Type
- With Generics
function useQuery(
schema: Queryable,
...args: SchemaArgs<typeof schema>
): DenormalizeNullable<typeof endpoint.schema> | undefined;
function useQuery<S extends Queryable>(
schema: S,
...args: SchemaArgs<S>
): DenormalizeNullable<S> | undefined;
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.
import { All, Query } from '@data-client/rest'; import { useQuery, useSuspense } from '@data-client/react'; import { type Sort, User, UserResource } from './resources/User'; import UsersTableView from './UsersTableView'; 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; }, ); export default function UsersTable({ sort, isAdmin, onSort }: Props) { useSuspense(UserResource.getList); const users = useQuery(sortedUsers, { ...sort, isAdmin }) ?? []; return <UsersTableView users={users} sort={sort} onSort={onSort} />; } interface Props { sort: Sort; isAdmin: boolean | undefined; onSort: (sort: Sort) => void; }
Remaining Todo total
Queries can also be used to compute aggregates
Explore the todo-app example
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.
import { All } from '@data-client/rest'; import { useQuery, useFetch } from '@data-client/react'; import { DepartmentResource, Department } from './Resources'; function BuildingList({ dept }: { dept: Department }) { const buildings = useQuery( Department.schema.buildings.query, dept.buildings, ); if (!buildings) return null; return <span>{buildings.map(b => b.name).join(', ')}</span>; } function DepartmentsPage() { useFetch(DepartmentResource.getList); const departments = useQuery(new All(Department)); if (!departments) return <Loading />; return ( <div> {departments.map(dept => ( <div key={dept.pk()}> <strong>{dept.name}</strong>: <BuildingList dept={dept} /> </div> ))} </div> ); } render(<DepartmentsPage />);
Data fallbacks
In this case Ticker is constantly updated from a websocket stream. However, there is no bulk/list
fetch for Ticker - making it inefficient for getting the prices on a list view.
So in this case we can fetch a list of Stats as a fallback since it has price data as well.
Explore the coin-app example