Table of Contents

Queries

Queries in the frontend is divided into the following:

  • The underlying IQueryFor<>, IObservableQueryFor<> interfaces
  • The React hooks; useQuery() and useObservableQuery() for standard usage, and useSuspenseQuery() / useSuspenseObservableQuery() for React Suspense boundaries
  • Proxy generator that generates TypeScript from the C# code to leverage the constructs.

HTTP Headers

Queries automatically include any HTTP headers provided by the httpHeadersCallback configured in the Arc. This enables you to dynamically include authentication cookies, authorization tokens, or other custom headers with every query request without manual configuration for each query.

Proxy Generation

Starting with the latter; the proxy generator you'll get the queries generated directly to use in the frontend. The proxies generated can be imported directly into your code.

Query

From a React component you can now use the static use() method:

export const MyComponent = () => {
    const [accounts, queryAccounts] = AllAccounts.use();

    return (
        <>
        </>
    )
};

Note: All data resulting from a query will be strongly typed based on the metadata provided by the proxy generator.

Return tuple

If the query is a regular request / response type of query, the tuple returned contains two elements. If it is an observable query, it only returns the first element of the tuple.

The return values are:

  • The query result
  • Delegate for issuing the query again

QueryResultWithState

The query result returned is a type called QueryResultWithState this is a sub type of QueryResult adding properties that are relevant when working in React.

From the base QueryResult one gets the following properties:

Property Description
data The actual data returned in the type expected.
isSuccess Boolean telling whether or not the query was successful or not.
isAuthorized Boolean telling whether or not the query was authorized or not.
isValid Boolean telling whether or not the query was valid or not.
ValidationResult Collection with any validation errors.
hasExceptions Boolean telling whether or not the query had exceptions or not.
exceptionMessages Collection with any exception messages.
exceptionStackTrace The stack trace for the exception if there was one.
paging Contains paging information, with current page number, page size, total number of items and total number pages

On top of this QueryResultWithState adds the following properties:

Property Description
hasData Boolean indicating whether or not there is data in the result.
isPerforming Boolean that is true when an operation is working to get data from the server.

Parameters

Queries can have parameters they can be used for instance for filtering. Lets say you have a query called StartingWith:

[HttpGet("starting-with")]
public IEnumerable<DebitAccount> StartingWith([FromQuery] string? filter)
{
    var filterDocument = Builders<DebitAccount>
        .Filter
        .Regex("name", $"^{filter ?? string.Empty}.*");

    return _collection.Find(filterDocument).ToList();
}

The filter parameter will be part of the generated proxy, since it has the [FromQuery] attribute on it. Using the proxy requires you to now specify the parameter as well:

export const MyComponent = () => {
    const [accounts, queryAccounts] = StartingWith.use({ filter: '' });

    return (
        <>
        </>
    )
};

Note: Route values will also be considered parameters and generated when adorning a method parameter with [HttpPost].

Suspense-Compatible Hooks

Arc also provides Suspense-compatible variants of these hooks — useSuspenseQuery() and useSuspenseObservableQuery() — that integrate with React's <Suspense> and ErrorBoundary patterns, letting components declaratively describe loading and error states without inspecting isPerforming or hasExceptions manually.

See Suspense Queries for details and examples.

Paging

When the backend query returns IQueryable<T>, the query pipeline applies server-side paging automatically — appending .Skip() and .Take() at the database level so only the requested page of data is fetched. The generated TypeScript proxy includes useWithPaging and useSuspenseWithPaging methods.

Enabling paging

Use useWithPaging instead of use, passing the desired page size:

export const AccountList = () => {
    const [result, perform, setSorting, setPage, setPageSize] = AllAccounts.useWithPaging(25);

    return (
        <>
            <DataTable value={result.data}>
                <Column field="name" header="Name" />
                <Column field="balance" header="Balance" />
            </DataTable>
            <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>
        </>
    );
};

Return tuple for paged queries

The useWithPaging hook returns an extended tuple:

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

Paging metadata

Paging information is available on result.paging:

Property Type Description
page number Current zero-based page number
size number Items per page
totalItems number Total items across all pages
totalPages number Total number of pages

All hook variants with paging

Hook Description
MyQuery.useWithPaging(pageSize) Standard query with paging
MyQuery.useSuspenseWithPaging(pageSize) Suspense-compatible with paging
MyObservableQuery.useWithPaging(pageSize) Observable query with paging

Each variant accepts an optional args parameter (for filtered queries) and an optional sorting parameter.

Sorting

Sorting is independent of paging and works with all query hooks. Pass a Sorting instance to the hook or use the setSorting callback:

import { Sorting, SortDirection } from '@cratis/arc/queries';

// Initial sorting
const [result] = AllAccounts.use(undefined, new Sorting('name', SortDirection.ascending));

// Change sorting dynamically
const [result, perform, setSorting] = AllAccounts.use();
await setSorting(new Sorting('balance', SortDirection.descending));

Important: Automatic paging requires the backend to return IQueryable<T>. If the backend returns IEnumerable<T> or List<T>, the paging parameters are sent but ignored — all rows are returned in a single response.