Query
Query provides programmatic access to the Reactive Data Client cache while maintaining
the same high performance and referential equality guarantees expected of Reactive Data Client.
Query can be rendered using schema lookup hook useQuery()
Query members
schema
Schema used to retrieve/denormalize data from the Reactive Data Client cache. This accepts any Queryable schema: Entity, All, Collection, Query, Union, and Object schemas for joining multiple entities.
process(entries, ...args)
Takes the (denormalized) response as entries and arguments and returns the new response for use with useQuery
Usage
Maintaining sort after creates
import { Collection, Entity, Query, RestEndpoint } from '@data-client/rest'; export class Post extends Entity { id = ''; title = ''; group = ''; author = ''; } export const getPosts = new RestEndpoint({ path: '/:group/posts', searchParams: {} as { orderBy?: string; author?: string }, schema: new Query( new Collection([Post], { nonFilterArgumentKeys: /orderBy/, }), (posts, { orderBy } = {}) => { if (orderBy) { return [...posts].sort((a, b) => a[orderBy].localeCompare(b[orderBy]), ); } return posts; }, ), });
Aggregates
[{"id":"123","name":"Jim"},{"id":"456","name":"Jane"},{"id":"777","name":"Albatras","isAdmin":true}]
import { All, Query } from '@data-client/rest'; import { useQuery, useFetch } from '@data-client/react'; import { UserResource, User } from './resources/User'; const countUsers = new Query( new All(User), (entries, { isAdmin } = {}) => { if (isAdmin !== undefined) return entries.filter(user => user.isAdmin === isAdmin).length; return entries.length; }, ); function UsersPage() { useFetch(UserResource.getList); const userCount = useQuery(countUsers); const adminCount = useQuery(countUsers, { isAdmin: true }); if (userCount === undefined) return <div>No users in cache yet</div>; return ( <div> <div>Total users: {userCount}</div> <div>Total admins: {adminCount}</div> </div> ); } render(<UsersPage />);
Rearranging data with groupBy aggregations
import { Query } from '@data-client/rest'; import { useQuery, useFetch, useSuspense } from '@data-client/react'; import { TodoResource } from './resources/Todo'; import { UserResource } from './resources/User'; import TodoByUser from './TodoByUser'; const groupTodoByUser = new Query( TodoResource.getList.schema, todos => Object.groupBy(todos, todo => todo.userId), ); function TodosPage() { useFetch(UserResource.getList); useSuspense(TodoResource.getList); useSuspense(UserResource.getList); const todosByUser = useQuery(groupTodoByUser); if (!todosByUser) return <div>Todos not found</div>; return ( <div> {Object.keys(todosByUser).slice(5).map(userId => ( <TodoByUser key={userId} userId={userId} todos={todosByUser[userId]} /> ))} </div> ); } render(<TodosPage />);
Object Schema Joins
Query can take Object Schemas, enabling joins across multiple entity types. This allows you to combine data from different entities in a single query.
{"product_id":"BTC-USD","price":45000}
{"product_id":"BTC-USD","last":44950}
import { Query } from '@data-client/rest'; import { useQuery, useFetch } from '@data-client/react'; import { TickerResource, Ticker } from './resources/Ticker'; import { StatsResource, Stats } from './resources/Stats'; // Join Ticker and Stats by product_id const queryPrice = new Query( { ticker: Ticker, stats: Stats }, ({ ticker, stats }) => ticker?.price ?? stats?.last, ); function PriceDisplay({ productId }: { productId: string }) { useFetch(TickerResource.get, { product_id: productId }); useFetch(StatsResource.get, { product_id: productId }); const price = useQuery(queryPrice, { product_id: productId }); if (price === undefined) return <div>Loading...</div>; return <div>Price: ${price}</div>; } render(<PriceDisplay productId="BTC-USD" />);
Fallback joins
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.