# Tanstack Query Intent Core Build Query Abstractions

> Use this when creating TanStack Query abstractions: queryOptions factories, feature-local key modules, custom hooks built on top of options factories, TypeScript inference, avoiding wide UseQueryOptions wrappers, and sharing query configuration across hooks, loaders, prefetches, and QueryClient calls.

- Skill: `lukasa1993/tanstack-query-intent-core-build-query-abstractions` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add lukasa1993/tanstack-query-intent-core-build-query-abstractions`
- Raw SKILL.md: https://api.skillmd.com/api/skills/lukasa1993/tanstack-query-intent-core-build-query-abstractions/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- License: MIT
- Author: lukasa1993 (https://skillmd.com/u/lukasa1993)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/lukasa1993/tanstack-query-intent-core-build-query-abstractions

---


## Core Patterns

Prefer a `queryOptions` factory as the base abstraction. Hooks, loaders, prefetches, suspense queries, and `QueryClient` calls can all consume it.

```ts
import { queryOptions, useQuery, useSuspenseQuery } from '@tanstack/react-query'

export function invoiceOptions(id: number) {
  return queryOptions({
    queryKey: ['invoice', id],
    queryFn: () => fetchInvoice(id),
    staleTime: 60_000,
  })
}

export function useInvoice(id: number) {
  return useQuery(invoiceOptions(id))
}

export function useSuspenseInvoice(id: number) {
  return useSuspenseQuery(invoiceOptions(id))
}
```

Compose one-off options at the usage site:

```ts
const invoice = useQuery({
  ...invoiceOptions(id),
  select: (data) => data.createdAt,
  throwOnError: true,
})
```

## Common Mistakes

### HIGH Custom hook is the only abstraction

Wrong:

```ts
export function useInvoice(id: number) {
  return useQuery({
    queryKey: ['invoice', id],
    queryFn: () => fetchInvoice(id),
  })
}
```

Correct:

```ts
export function invoiceOptions(id: number) {
  return queryOptions({
    queryKey: ['invoice', id],
    queryFn: () => fetchInvoice(id),
  })
}

export function useInvoice(id: number) {
  return useQuery(invoiceOptions(id))
}
```

Custom hooks cannot run in route loaders, server prefetches, or event handlers. Options factories can.

Source: https://tkdodo.eu/blog/creating-query-abstractions

### HIGH Wide UseQueryOptions wrapper breaks inference

Wrong:

```ts
function useInvoice(id: number, options?: Partial<UseQueryOptions<Invoice>>) {
  return useQuery({
    queryKey: ['invoice', id],
    queryFn: () => fetchInvoice(id),
    ...options,
  })
}
```

Correct:

```ts
useQuery({
  ...invoiceOptions(id),
  select: (invoice) => invoice.createdAt,
})
```

Let `queryOptions` and usage-site composition preserve `select` inference.

Source: https://tkdodo.eu/blog/creating-query-abstractions

### MEDIUM Wrapper hides Query result state

Wrong:

```ts
export function useInvoice(id: number) {
  const { data } = useQuery(invoiceOptions(id))
  return data
}
```

Correct:

```ts
export function useInvoice(id: number) {
  return useQuery(invoiceOptions(id))
}
```

Keep the Query result surface available unless the abstraction owns every loading, error, and refetch behavior.

Source: TanStack/query:docs/framework/react/guides/queries.md

