schema.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, and Union.
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
Here we have an API that sorts based on the orderBy
field. By wrapping our Collection
in a Query
that sorts, we can ensure we maintain the correct order after pushing
new posts.
Our example code starts sorting by title
. Try adding some posts and see them inserted in the correct sort
order.
import { Entity, RestEndpoint } from '@data-client/rest'; class Post extends Entity { id = ''; title = ''; group = ''; author = ''; pk() { return this.id; } } export const getPosts = new RestEndpoint({ path: '/:group/posts', searchParams: {} as { orderBy?: string; author?: string }, schema: new schema.Query( new schema.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 { schema } from '@data-client/rest'; import { useQuery, useFetch } from '@data-client/react'; import { UserResource, User } from './api/User'; const getUserCount = new schema.Query( new schema.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(getUserCount); const adminCount = useQuery(getUserCount, { 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 />);
Client side joins
Even if the network responses don't nest data, we can perform client-side joins by specifying the relationship in Entity.schema
import { schema } from '@data-client/rest'; import { useQuery, useFetch } from '@data-client/react'; import { TodoResource, Todo } from './api/Todo'; import { UserResource, User } from './api/User'; const todosWithUser = new schema.Query( new schema.All(Todo), (entries, { userId = 0 }) => { return entries.filter(todo => todo.userId?.id === userId); }, ); function TodosPage() { useFetch(UserResource.getList); useFetch(TodoResource.getList); const todos = useQuery(todosWithUser, { userId: 1 }); if (!todos) return <div>No Todos in cache yet</div>; if (!todos.length) return <div>No Todos match user</div>; return ( <div> {todos.map(todo => ( <div key={todo.pk()}> {todo.title} by {todo.userId.name} </div> ))} </div> ); } render(<TodosPage />);
Rearranging data with groupBy aggregations
import { schema } from '@data-client/rest'; import { useQuery, useFetch } from '@data-client/react'; import { TodoResource, Todo } from './api/Todo'; import { UserResource } from './api/User'; const groupTodoByUser = new schema.Query( TodoResource.getList.schema, todos => { return Object.groupBy(todos, todo => todo?.userId?.username) as Record< string, Todo[] >; }, ); function TodosPage() { useFetch(UserResource.getList); useSuspense(TodoResource.getList); useSuspense(UserResource.getList); const todoByUser = useQuery(groupTodoByUser); if (!todoByUser) return <div>Todos not found</div>; return ( <div> {Object.keys(todoByUser).map(username => ( <div key={username}> <h3> {username} has {tasksRemaining(todoByUser[username])} tasks left </h3> {todoByUser[username].slice(0, 3).map(todo => ( <div key={todo.pk()}> {todo.title} by {todo?.userId?.name} </div> ))} </div> ))} </div> ); } function tasksRemaining(todos: Todo[]) { return todos.filter(({ completed }) => !completed).length; } render(<TodosPage />);