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.
class Query<S extends SchemaSimple, P extends any[] = []> {
constructor(
schema: S,
process?: (entries: Denormalize<S>, ...args: P) => Denormalize<S>,
);
schema: S;
key(...args: P): string;
process: (entries: Denormalize<S>, ...args: P) => Denormalize<S>;
}
Query
implements the EndpointInterface but without the fetch function, which
means it can only be passed to the data binding hook useCache()
Query members
schema
Schema used to retrieve/denormalize data from the Reactive Data Client cache. Most cases will use schema.All, which retrieves all entities of a given type found in the cache.
process(entries, ...args)
Takes the (denormalized) response as entries and arguments and returns the new response for use with useCache
key(...args)
Implements Endpoint.key Used to determine recomputation of memoized values.
Usage
Simplest
[{"id":"123","name":"Jim"},{"id":"456","name":"Jane"}]
import { Query, schema } from '@data-client/rest'; import { UserResource, User } from './api/User'; const allUsers = new Query(new schema.All(User)); function UsersPage() { useFetch(UserResource.getList); const users = useCache(allUsers); 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 />);
Sorting & Filtering
[{"id":"123","name":"Jim"},{"id":"456","name":"Jane"},{"id":"777","name":"Albatras","isAdmin":true}]
import { Query, schema } from '@data-client/rest'; import { UserResource, User } from './api/User'; interface Args { asc: boolean; isAdmin?: boolean; } const sortedUsers = new Query( new schema.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 = useCache(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 />);
Aggregates
[{"id":"123","name":"Jim"},{"id":"456","name":"Jane"},{"id":"777","name":"Albatras","isAdmin":true}]
import { Query, schema } from '@data-client/rest'; import { UserResource, User } from './api/User'; const getUserCount = new 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 = useCache(getUserCount); const adminCount = useCache(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 { Query, schema } from '@data-client/rest'; import { TodoResource, Todo } from './api/Todo'; import { UserResource, User } from './api/User'; const todosWithUser = new 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 = useCache(todosWithUser, { userId: 1 }); if (!todos) return <div>No Todos in cache yet</div>; return ( <div> {todos.map(todo => ( <div key={todo.pk()}> {todo.title} by {todo.userId.name} </div> ))} </div> ); } render(<TodosPage />);