Skip to content
effect-api-query

Conditional Queries

Pause queries until their required input is available.

Use { input: skipToken } when an input-bearing query has no valid input yet and needs TanStack options such as staleTime, select, or initialData:

import { skipToken } from 'effect-api-query'
type User = { id: number; name: string }
const displayOptions = { staleTime: 30_000, select: (user: User) => user.name }
const userOptions = rpcQuery.users.get.queryOptions({
...displayOptions,
input: userId === undefined ? skipToken : { id: userId },
})
const user = useQuery(userOptions)

The same form works for HTTP request input:

const httpUserOptions = http.users.get.queryOptions({
input: userId === undefined ? skipToken : { params: { id: userId } },
staleTime: 30_000,
select: (user) => user.name,
})

The exported skipToken is Query Core’s exact sentinel. A skipped query uses its operation prefix as its key, without payload or request identity. Skipping an HTTP query does not encode a request or invoke the client. The builder preserves caller options and their selected-data types, and removes input before returning the options to TanStack.

Supplied initialData remains available. React Query still types skipped hook data as possibly undefined, even with an initial value, because its defined-data overload excludes skipToken.

The sentinel applies to input-bearing queryOptions and infiniteOptions in both adapters, and to RPC streamedOptions and liveOptions. Inputless operations run without input, and key and mutation builders do not accept skipToken. TanStack suspense and prefetch-only hooks also reject skipped options at the type level.

Unary queryOptions accepts an input that may be a valid RPC payload or HTTP request, or skipToken. Keep the conditional input inside one builder call so the observer has one consistent callback type. Concrete inputs and literal skipToken keep their precise key types.

The object form also works for accumulated streams and live queries:

rpcQuery.events.watch.streamedOptions({
input: skipToken,
refetchMode: 'append',
staleTime: 30_000,
})
rpcQuery.events.watch.liveOptions({ input: skipToken, select: (value) => value.length })

refetchMode configures accumulation. The builder removes it from the returned options even when the query is skipped. Infinite queries use { input: skipToken } with their required initialPageParam and getNextPageParam.

When no caller options are needed, queryOptions(skipToken), streamedOptions(skipToken), and liveOptions(skipToken) remain available as shorthand. A skipped query has no executable query function, so manual refetch() cannot run it. Supply valid input to enable it. If a complete request is available and you need manual refetch, use enabled: false instead.

For HTTP pagination, map each page parameter to the complete request and keep stable filters in every page. See Load pages for initial-request identity and cursor progression.

Try this in either executable example. The Choose before fetching control demonstrates pausing, selecting a user, and reusing fresh cached data after clearing and reselecting the same user.

The public RPC consumer and public HTTP consumer check skipped hook inference and rejected uses.