SuperAPI AI Directory

tanstack-query-intent-core-build-query-abstractions

Frontend & websites

View repository

Updated 10-16 00:02

About this skill

Core Patterns

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

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:

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

Correct:

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:

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

Correct:

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:

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

Correct:

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