# useQuery

Fetch data with automatic caching, refetching, and full type inference from your API definitions.

## Signature

Access `useQuery` through the query caller's dot-notation API. Types are fully inferred from your endpoint definitions:

```ts
const result = q.users.useQuery("getAll", options)

// q.<group>.useQuery(method, options?)
// group   — endpoint group name (e.g. "users"), or a group inside one: q.billing.invoices
// method  — method name within the group (e.g. "getAll")
// options — { variables?, requestOptions?, ...reactQueryOptions }
```

## Basic query

The simplest usage — fetch data on mount. The `data` type is automatically inferred from the method's response type:

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

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

export function UserList() {
  const { data, isLoading, error } = q.users.useQuery("getAll")
  //      ^? UserList — auto-inferred from .response<UserList>()

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

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

## Query with variables

When a method defines variables (e.g. `.variables<{ id: number }>()`), the `variables` option becomes required. TypeScript will error if you omit it:

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

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

export function UserProfile({ userId }: { userId: number }) {
  // .variables<{ id: number }>() → variables is required
  const { data: user, isLoading } = q.users.useQuery("getById", {
    variables: { id: userId },
    //          ^? { id: number } — auto-inferred from the chain
  })

  if (isLoading) return <p>Loading...</p>

  return (
    <div>
      <h1>{user?.name}</h1>
      <p>{user?.email}</p>
    </div>
  )
}
```

> **Variables type safety**
>
> If a method never calls `.variables()`, the `variables` option is optional. If it declares a concrete variables type, the options object with `variables` is required.

## Conditional queries

Use the `enabled` option to conditionally execute a query. The query won't fire until the condition is true:

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

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

export function UserProfile({ userId }: { userId: number | null }) {
  const { data, isLoading } = q.users.useQuery("getById", {
    variables: { id: userId! },
    // Only fetch when userId is available
    enabled: userId !== null,
  })

  if (!userId) return <p>Select a user</p>
  if (isLoading) return <p>Loading...</p>

  return <h1>{data?.name}</h1>
}
```

## React Query options

All standard React Query options are supported alongside Courier-specific options. Configure caching, refetching, and data transformation:

```ts
const { data } = q.users.useQuery("getAll", {
  variables: { page: 1, limit: 20 },

  // Standard React Query options — all supported
  staleTime: 5 * 60 * 1000,       // Consider data fresh for 5 minutes
  refetchInterval: 30 * 1000,     // Refetch every 30 seconds
  refetchOnWindowFocus: false,     // Don't refetch on tab focus
  placeholderData: previousData,   // Keep previous data while refetching
  select: (data) => data.data,     // Transform response before caching

  // Courier-specific options
  requestOptions: {
    timeout: 5000,   // The options .with() takes, per call
  },
})
```

## Auto-generated query keys

Courier automatically generates hierarchical query keys from the source, group, method, and variables. This enables cache invalidation at different levels of specificity:

```ts
// Auto-generated query key: ["api", "users", "getAll"]
q.users.useQuery("getAll")

// With variables: ["api", "users", "getById", { id: 5 }]
q.users.useQuery("getById", { variables: { id: 5 } })

// Invalidate a specific method
queryClient.invalidateQueries({ queryKey: ["api", "users", "getAll"] })

// Invalidate all queries for a group
queryClient.invalidateQueries({ queryKey: ["api", "users"] })

// Invalidate all queries for the source
queryClient.invalidateQueries({ queryKey: ["api"] })
```

> **Group-level invalidation**
>
> Unlike flat key formats, hierarchical keys let you invalidate all queries for a group at once. `["api", "users"]` matches `getAll`, `getById`, `create` — everything in the `users` group.

## Error handling

The `error` property is typed as `Error`, as TanStack Query types it, because not every failure is an HTTP one. It is an `HTTPError` for an error answer or a call that got no answer, a `ValidationError` when variables or the answer fail their schema, a `ConfigurationError` when the client is set up wrongly, the reason a call was cancelled with through your own signal, or whatever a `select` threw. The same holds for `useSuspenseQuery`, `useInfiniteQuery`, `useMutation` and `useComputedQuery`. Use `isHTTPError` to check if it's an HTTP error with status and response details, and `isValidationError` or `isConfigurationError` for the others:

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

import { isHTTPError } from "@noverax/courier"
import { q } from "@/lib/query"

export function UserList() {
  const { data, error, isError } = q.users.useQuery("getAll")

  if (isError) {
    // Check if it's an HTTP error with status/response
    if (isHTTPError(error)) {
      if (error.status === 404) return <p>Not found</p>
      if (error.status === 403) return <p>Access denied</p>
    }
    return <p>Something went wrong: {error.message}</p>
  }

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

## Next steps

- [useMutation](/docs/react-query/use-mutation) — create, update, and delete data with typed mutations
- [Advanced Patterns](/docs/react-query/advanced) — prefetching, hydration, optimistic updates, and cache strategies
- [Error Handling](/docs/core-concepts/error-handling) — deep dive into HTTPError and error recovery
