Core Query Usage
Use generated query proxies to retrieve data in React with strong typing and predictable state.
Proxy Generation
Section titled “Proxy Generation”Arc generates query proxies from backend queries (controller-based and model-bound). These proxies expose static React helpers such as .use() and .useWithPaging().
See Backend Proxy Generation for setup details.
Basic Query Hook
Section titled “Basic Query Hook”From a React component, call the generated static use() method:
export const MyComponent = () => { const [accounts, queryAccounts] = AllAccounts.use();
return ( <> </> );};All results are strongly typed from backend metadata.
Return Tuple
Section titled “Return Tuple”Use the tuple shape emitted for your backend result. Omitting trailing elements while destructuring is fine; treating the tuple itself as the model is not.
| Proxy shape | .use() / .useSuspense() | .useWithPaging() / .useSuspenseWithPaging() |
|---|---|---|
| Ordinary single or enumerable | [result, perform, setSorting] | Enumerable only: [result, perform, setSorting, setPage, setPageSize] |
| Observable enumerable | [result, setSorting] | [result, setSorting, setPage, setPageSize] |
| Observable single | [result] | Not generated |
Parameterized proxies emit NameParameters, not INameParameters. Their calls take args first and, for enumerables, optional sorting next. Parameterless enumerable calls take sorting first; parameterless single-result calls take no arguments. Paging prepends pageSize to these argument positions. For example, BooksForAuthor.use({ authorId }) passes a named argument object, whereas parameterless AllBooks.use(sorting) does not take an undefined argument placeholder.
Observable queries differ and are covered in Observable Queries.
QueryResultWithState
Section titled “QueryResultWithState”The query result type in React is QueryResultWithState. It implements the same IQueryResult contract as QueryResult and adds React-focused execution state.
Properties from the shared IQueryResult contract:
| Property | Description |
|---|---|
data | The actual data returned in the expected type. |
isSuccess | Whether the query completed successfully. |
isReady | Whether the query has produced a result. false is transient, not a failure. |
isAuthorized | Whether the query was authorized. |
isValid | Whether the query input was valid. |
validationResults | Validation errors returned by the backend. |
hasExceptions | Whether exceptions were returned. |
exceptionMessages | Exception messages from the backend. |
exceptionStackTrace | Exception stack trace when available. |
paging | Paging metadata (page, size, totals) when paging is enabled. |
React execution state:
| Property | Description |
|---|---|
isPerforming | Whether the query is currently fetching data. |
Readiness and performing state
Section titled “Readiness and performing state”An observable query HTTP request can return 202 Accepted with isReady: false before the observable has produced its first result. This is a transient server state: isSuccess is false, but hasExceptions remains false. Inspect isReady before treating an unsuccessful result as a failure.
isReady and isPerforming describe different things. Readiness says whether a result has been produced; performing says whether the client is currently fetching or subscribing. Initial React query state is not ready, while disabled or synthetic failure states are ready because no server result remains pending.
IQueryResult.isReady is optional for source compatibility: older Arc servers and third-party structural query-result objects may omit it. Arc normalizes query results received through its HTTP, WebSocket, and Server-Sent Events transports to isReady: true when the member is absent, while preserving an explicit false. SDK-produced QueryResult and QueryResultWithState instances always expose a concrete boolean.
Query Parameters
Section titled “Query Parameters”Queries can expose parameters on the backend — in C# through attributes such as [FromQuery] and route parameters, on the JVM through ordinary query-method parameters, and in TypeScript through @query(argument(...)) on a static read-model method. These become typed arguments on generated proxies.
[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();}@ReadModelpublic record DebitAccount(AccountId id, AccountName name) { public static List<DebitAccount> startingWith( String filter, @FromServices DebitAccountRepository accounts ) { return accounts.findByNameStartingWith(filter == null ? "" : filter); }}@ReadModeldata class DebitAccount(val id: AccountId, val name: AccountName) { companion object { @JvmStatic fun startingWith( filter: String?, @FromServices accounts: DebitAccountRepository ): List<DebitAccount> = accounts.findByNameStartingWith(filter ?: "") }}import { field } from '@cratis/fundamentals';import { argument, query, readModel, service } from '@cratis/arc.core';
@readModel()export class DebitAccount { @field(AccountId) id!: AccountId; @field(AccountName) name!: AccountName;
@query(argument('filter', String, { optional: true }), service(DebitAccountRepository)) static startingWith(filter: string | undefined, accounts: DebitAccountRepository): Promise<DebitAccount[]> { return accounts.findByNameStartingWith(filter ?? ''); }}The C# tab shows a controller action, because that is the style this attribute-driven parameter binding belongs to. The JVM has no controller-based discovery, so its parameters are simply the query method’s own, declared on the @ReadModel’s companion object. The TypeScript tab declares its argument explicitly with argument('filter', String, { optional: true }). All tabs use the shared frontend query runtime; their generated imports and paths depend on each backend’s configuration.
Use the parameterized proxy from React:
export const MyComponent = () => { const [accounts, queryAccounts] = StartingWith.use({ filter: '' });
return ( <> </> );};An enabled hook fetches again whenever any argument changes — optional arguments such as filter as well as required ones. Arguments are compared by their serialized value, so passing a new object with the same values does not trigger a request. Each set of arguments also gets its own cached result; see query instance caching.
HTTP Headers
Section titled “HTTP Headers”Ordinary query fetches use httpHeadersCallback. Native EventSource/WebSocket handshakes cannot attach those arbitrary headers; SSE control POSTs are separate fetch requests. Cookies are browser-managed, not configurable Cookie request headers. See the transport credentials matrix.