useQuery()
Data rendering without the fetch.
Access any Queryable Schema's store value; like Entity, All, Collection, Query,
and Union. 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 { 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 }); return ( <center> <small>{totalVotes} votes total</small> </center> ); } 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,
and Union. 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 provides programmatic access to the Reactive Data Client store.
import { Query } from '@data-client/rest'; import { useQuery, useFetch } from '@data-client/react'; import { UserResource, User } from './UserResource'; interface Args { asc: boolean; isAdmin?: boolean; } const sortedUsers = new Query( new All(User), (entries, { asc, isAdmin }: Args = { asc: false }) => { let sorted = [...entries].sort((a, b) => a.name.localeCompare(b.name)); if (isAdmin !== undefined) sorted = sorted.filter(user => user.isAdmin === isAdmin); if (asc) return sorted; return sorted.reverse(); }, ); function UsersPage() { useFetch(UserResource.getList); const users = useQuery(sortedUsers, { asc: true }); if (!users) return <div>No users in cache yet</div>; return ( <div> {users.map(user => ( <div key={user.pk()}>{user.name}</div> ))} </div> ); } render(<UsersPage />);
Remaining Todo total
Queries can also be used to compute aggregates
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 { 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 <div>Loading...</div>; 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.