Conditional Queries
Sometimes a component receives a query argument through a prop or local state that may not be available yet — for example, an entity ID that starts as undefined until the user makes a selection. Issuing the query before the value is available leads to an invalid request. At the same time, React's rules of hooks prohibit calling hooks inside if statements, so you cannot simply wrap use() in a conditional.
The when() pattern solves this. Every generated query class exposes a static when(condition) method that returns a QueryWhen (or ObservableQueryWhen) wrapper. The wrapper exposes the same use, useWithPaging, useSuspense, and useSuspenseWithPaging methods as the query class, but the underlying hook is a no-op when condition is false. When condition is true, behaviour is identical to calling the hook directly.
Generated API
The proxy generator emits a when() static method on every query and observable query class:
// Generated for a regular query
static when(condition: boolean): QueryWhen<AllAccounts, Account[], AllAccountsParameters>
// Generated for an observable query
static when(condition: boolean): ObservableQueryWhen<LiveFeed, Message[], LiveFeedParameters>
Basic Usage
import { AllAccounts } from './Proxies';
export const AccountDetail = ({ accountId }: { accountId: string | undefined }) => {
// Hook always runs — no conditional hook violation.
// When accountId is undefined the query is disabled.
const [accounts] = AllAccounts.when(!!accountId).use({ id: accountId ?? '' });
if (!accounts.hasData) return <p>Select an account.</p>;
return <div>{accounts.data.map(a => <p key={a.id}>{a.name}</p>)}</div>;
};
When condition is false:
- No HTTP request or WebSocket/SSE subscription is created.
- The hook returns
QueryResultWithState.empty()—hasDataisfalse,isPerformingisfalse,datais[]/undefineddepending on the query shape.
When condition becomes true on a subsequent render, the hook connects to the server and the component re-renders with real data.
With Paging
const [accounts] = AllAccounts.when(isReady).useWithPaging(20, args);
With Suspense
// Inside a <Suspense> boundary
const [feed] = LiveFeed.when(!!topicId).useSuspense({ topic: topicId ?? '' });
Observable Queries
The pattern works identically for observable queries via ObservableQueryWhen:
import { LiveFeed } from './Proxies';
export const FilteredFeed = ({ author }: { author: string }) => {
// Subscription only starts once author is non-empty
const [feed] = LiveFeed.when(author.length > 0).use({ author });
return <ul>{feed.data.map(m => <li key={m.id}>{m.text}</li>)}</ul>;
};
How It Interacts with QueryInstanceCache
When condition is false no cache entry is created or referenced. The moment condition turns true the hook resolves or creates a cache entry for the type + arguments combination. If another component has already populated that cache entry, the new subscriber receives the last known result immediately — no server round-trip needed. See Query Instance Caching for details.
Memoization
when() creates a lightweight wrapper object on every render. For components where the condition expression is complex or the render is performance-sensitive, you can memoize the result:
const query = useMemo(() => AllAccounts.when(!!accountId), [accountId]);
const [accounts] = query.use({ id: accountId ?? '' });
In practice the object is small enough that memoization is rarely needed.
isEnabled on Raw Hooks
Behind the scenes QueryWhen and ObservableQueryWhen delegate to the raw useQuery / useObservableQuery (and their *WithPaging and useSuspense* variants) by passing isEnabled as the last argument. The raw hooks also accept this parameter directly if you need low-level control:
import { useObservableQuery } from '@cratis/arc.react/queries';
const [result] = useObservableQuery(MyQuery, args, sorting, isEnabled);
The generated when() API is preferred for application code because it keeps intent explicit and co-located with the query class.