Skip to content

Paging

Enumerable query proxies expose paging helpers. The backend must implement paging: ordinary automatic pipeline paging uses IQueryable<T>, while observable providers can apply paging themselves (for example MongoDB Observe() uses the query context’s Skip/Limit).

When the backend returns IQueryable<T>, the query pipeline applies .Skip() and .Take() on the server so only the requested page is fetched. Generated proxies expose useWithPaging and useSuspenseWithPaging for this flow.

For backend implementation details, see Controller-based Paging and Model-bound Paging.

Use useWithPaging instead of use, passing a page size. This example assumes a generated, non-observable AllAccounts query proxy whose items have id, name, and balance; adjust the import to your proxy output path:

import { AllAccounts } from './Accounts/AllAccounts';
export const AccountList = () => {
const [result, perform, setSorting, setPage, setPageSize] = AllAccounts.useWithPaging(25);
return (
<>
<table>
<thead>
<tr><th>Name</th><th>Balance</th></tr>
</thead>
<tbody>
{result.data.map(account => (
<tr key={String(account.id)}>
<td>{account.name}</td>
<td>{account.balance}</td>
</tr>
))}
</tbody>
</table>
<p>
Page {result.paging.page + 1} of {result.paging.totalPages}
({result.paging.totalItems} total items)
</p>
<button
disabled={result.paging.page === 0}
onClick={() => setPage(result.paging.page - 1)}>
Previous
</button>
<button
disabled={result.paging.page >= result.paging.totalPages - 1}
onClick={() => setPage(result.paging.page + 1)}>
Next
</button>
</>
);
};

For an ordinary query, useWithPaging returns an extended tuple:

IndexNameTypeDescription
0resultQueryResultWithState<T>Query result including paging metadata
1perform() => Promise<void>Re-execute the query
2setSorting(sorting: Sorting) => Promise<void>Change sort field and direction
3setPage(page: number) => Promise<void>Navigate to a specific page (zero-based)
4setPageSize(pageSize: number) => Promise<void>Change number of items per page

For an observable enumerable query the tuple is [result, setSorting, setPage, setPageSize]; there is no perform delegate. Single-result proxies do not generate paging helpers. Suspense variants preserve these tuple shapes.

In non-Suspense observable hooks, setPage, setPageSize, and setSorting move to a cache entry for the requested paging and sorting. Each new subscription sends those settings to the server. Consumers on different pages or sort orders have independent subscriptions; consumers with matching query arguments, paging and sorting share one.

While the new page is pending, the result shows its cached data if available. Otherwise it shows the query’s default value with isReady: false and isPerforming: true, not the previous page’s rows. Returning to a retained entry reuses its cached result and live subscription without subscribing again.

Ordinary paged queries also reset to the matching entry, but perform a fresh request and mark cached data as isPerforming: true until it settles. Suspense uses separate resource caches. See query instance caching for retention and sharing.

Paging information is available on result.paging:

PropertyTypeDescription
pagenumberCurrent zero-based page number
sizenumberItems per page
totalItemsnumberTotal items across all pages
totalPagesnumberTotal number of pages
HookDescription
MyQuery.useWithPaging(pageSize)Standard query with paging
MyQuery.useSuspenseWithPaging(pageSize)Suspense-compatible query with paging
MyObservableQuery.useWithPaging(pageSize)Observable paging with a subscription for the selected page and sort order

Parameterized proxies accept (pageSize, args?, sorting?); parameterless enumerable proxies accept (pageSize, sorting?). Match the generated signature rather than inserting an arguments placeholder for a parameterless query.

Sorting is independent of paging. Changing sort order fetches the matching ordinary-query result or selects the matching observable subscription. The following ordinary-query fragment shows initial sorting and a dynamic change.

import { Sorting, SortDirection } from '@cratis/arc/queries';
// Initial sorting
const [result] = AllAccounts.use(new Sorting('name', SortDirection.ascending));
// Change sorting dynamically
const [paged, perform, setSorting] = AllAccounts.useWithPaging(25);
await setSorting(new Sorting('balance', SortDirection.descending));

Ordinary automatic query-pipeline paging requires IQueryable<T>. A materialized IEnumerable<T> or List<T> does not gain automatic server slicing merely because a paging hook exists. An application/provider may explicitly consume paging context, including for observable ISubject results. Verify both returned rows and paging metadata against that provider.