# createProxyQueryCaller

Creates a proxy-mode React Query client with dot-notation hooks for every endpoint group, routed through the server adapter.

## Import

```ts
import { createProxyQueryCaller } from "@noverax/courier-react"
```

> **Peer dependency**
>
> `@noverax/courier-react` requires `@tanstack/react-query` as a peer dependency. It re-exports `QueryClient`, `QueryClientProvider`, `useQueryClient`, `HydrationBoundary`, and `dehydrate` for convenience.

## Signature

Accepts the routes of an API — the endpoints a page may call and how it reaches each, as Courier writes them into `courier.routes.ts` from the endpoints marked `.expose()` — and `ProxyCallerOptions`, as `createProxyCaller` does. Returns a Proxy-based client with dot-notation access to hooks for each group, typed from the API the routes were written from, for the endpoints they name alone.

```ts
import type { ProxyCallerOptions } from "@noverax/courier"
import type { CourierRoutes, DefinedApi, RoutedApi, RouteShape } from "@noverax/courier/define"
import type { QueryCallerClient } from "@noverax/courier-react"

declare function createProxyQueryCaller<
  TApi extends DefinedApi,
  TEndpoints extends Readonly<Record<string, RouteShape>>,
>(
  routes: CourierRoutes<TApi, TEndpoints>,
  options?: ProxyCallerOptions,
): QueryCallerClient<RoutedApi<TApi, TEndpoints>>
```

## Return Type

The returned object provides dot-notation access to every group defined in your API. Each group exposes:

```ts
type QueryGroupClient<G> = {
  useQuery(method, options?): UseQueryResult<R, Error>
  useSuspenseQuery(method, options?): UseSuspenseQueryResult<R, Error>
  useInfiniteQuery(method, options): UseInfiniteQueryResult<R, Error>
  useMutation(method, options?): UseMutationResult<R, Error, V>
  prefetchQuery(queryClient, method, options?): Promise<void>
}
```

A hook's error is typed `Error`, as TanStack Query types it, because not every failure is an HTTP one: narrow it with `isHTTPError`, `isValidationError` or `isConfigurationError` from `@noverax/courier`. See [useQuery — Error handling](/docs/react-query/use-query#error-handling).

## Setup

```ts title="lib/query.ts"
import { createProxyQueryCaller } from "@noverax/courier-react"
import { apiRoutes } from "@/courier.routes"

export const q = createProxyQueryCaller(apiRoutes, {
  proxyPath: { http: "/api/request" },  // optional, defaults to /api/request
  timeout: 10000,
  credentials: "include",
})
```

The `options` argument is a `ProxyCallerOptions` (same shape as [`createProxyCaller`](/docs/api/create-proxy-caller)). Notable fields:

- `proxyPath` — per-transport endpoint paths. `http` and `sse` have built-in defaults; `ws` is mandatory for `useChannel` and has no default. The routes do not say it: it depends on where the page is served.
- `ws` — options of the socket `useChannel` opens: the token, the reconnect backoff, the heartbeat and the queue limits — see [Connection options](/docs/advanced/realtime#connection-options).

The routes are what the hooks are typed from, and all they know of the API: see [Exposing Endpoints](/docs/proxy-mode/expose). A hook asked for an endpoint they do not name — from JavaScript, or past a cast — fails with `ConfigurationError("ENDPOINT_NOT_IN_ROUTES")` before anything is sent, since the proxy would refuse it.

> **useChannel requires proxyPath.ws**
>
> On runtimes that cannot host WebSockets in-process (Next.js App Router, Cloudflare Workers, Vercel Edge, AWS Lambda) you must point `proxyPath.ws` at a [standalone `@noverax/courier-server`](/docs/deployment/standalone-server). A `useChannel` without `proxyPath.ws` configured reports `ConfigurationError("WS_PATH_NOT_CONFIGURED")` through its `error` and `onError` when it first opens.

## useQuery

Wraps `@tanstack/react-query`'s `useQuery`. All standard options are supported.

```tsx title="components/user-list.tsx"
"use client"

import { q } from "@/lib/query"

export function UserList() {
  const { data, isLoading, error } = q.users.useQuery("getAll", {
    staleTime: 60_000,
    refetchOnWindowFocus: false,
  })

  if (isLoading) return <p>Loading...</p>
  if (error) return <p>Error: {error.message}</p>

  return (
    <ul>
      {data?.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  )
}
```

### Variables

When an endpoint expects variables, the `variables` option becomes required — enforced by TypeScript.

```ts
const { data } = q.users.useQuery("getById", {
  variables: { id: "123" },
  enabled: !!id,
})
```

## useMutation

Variables are passed to the `mutate` function, not to the hook.

```tsx title="components/create-user.tsx"
"use client"

import { q } from "@/lib/query"
import { useQueryClient } from "@noverax/courier-react"

export function CreateUserForm() {
  const queryClient = useQueryClient()

  const { mutate, isPending } = q.users.useMutation("create", {
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ["api", "users", "getAll"] })
    },
  })

  return (
    <form onSubmit={(e) => {
      e.preventDefault()
      const formData = new FormData(e.currentTarget)
      mutate({ name: formData.get("name") as string })
    }}>
      <input name="name" required />
      <button disabled={isPending}>
        {isPending ? "Creating..." : "Create"}
      </button>
    </form>
  )
}
```

## Query Keys

Query keys are auto-generated with a hierarchical format: `[source, group, method, variables?]`:

```ts
// Without variables: [source, group, method]
// With variables:    [source, group, method, variables]

q.users.useQuery("getAll")          // → ["api", "users", "getAll"]
q.users.useQuery("getById", {
  variables: { id: "123" }
})                                   // → ["api", "users", "getById", { id: "123" }]

// An infinite query adds its first page's variables and an "infinite" marker,
// so each filter gets its own list and no plain query shares its entry
q.posts.useInfiniteQuery("list", {
  variables: (cursor) => ({ cursor, tag: "news" }),
  initialPageParam: 0,
  getNextPageParam: (page) => page.nextCursor,
})                                   // → ["api", "posts", "list", { cursor: 0, tag: "news" }, "infinite"]

// Cache invalidation
queryClient.invalidateQueries({ queryKey: ["api", "users", "getAll"] })
queryClient.invalidateQueries({ queryKey: ["api", "users"] })  // entire group
queryClient.invalidateQueries({ queryKey: ["api"] })            // entire source
```

## Per-Request Options

Use `requestOptions` to pass `CallOptions` alongside React Query options:

```ts
const { data } = q.users.useQuery("getAll", {
  requestOptions: {
    timeout: 5000,
    method: "POST",  // keep this call's variables out of the address
  },
  staleTime: 30_000,
})

const { mutate } = q.files.useMutation("upload", {
  requestOptions: {
    onUploadProgress: (progress) => {
      setProgress(progress.percent)
    },
  },
})
```

## prefetchQuery

Prefetch data in Server Components for instant hydration on the client. On the server there is no page to resolve a relative `proxyPath` against, so a proxy caller cannot run there unless `proxyPath.http` is an absolute URL. Prefetch with a direct caller built from the definition instead — the server has it — and name the definition after the proxy's source, so both build the same keys:

```ts title="lib/query.server.ts"
import { createQueryCaller } from "@noverax/courier-react"
import { api } from "@/api" // defineApi({ name: "api", ... })

export const qServer = createQueryCaller(api)
```

```ts title="app/users/page.tsx"
import { dehydrate, HydrationBoundary, QueryClient } from "@noverax/courier-react"
import { qServer } from "@/lib/query.server"

export default async function UsersPage() {
  const queryClient = new QueryClient()

  await qServer.users.prefetchQuery(queryClient, "getAll")

  return (
    <HydrationBoundary state={dehydrate(queryClient)}>
      <UserList />
    </HydrationBoundary>
  )
}
```

## Related

- [React Query Setup](/docs/react-query/setup) — full setup guide with QueryClientProvider
- [useQuery Guide](/docs/react-query/use-query) — in-depth usage patterns
- [useMutation Guide](/docs/react-query/use-mutation) — mutations with optimistic updates
- [createProxyCaller](/docs/api/create-proxy-caller) — the underlying proxy caller
- [Exposing Endpoints](/docs/proxy-mode/expose) — the marks, and the routes Courier writes from them
