Capítulo 6 de 80

Chapter 6: TypeScript

Core Idea

Types flow through useQuery/useMutation automatically from a well-typed queryFn/mutationFn — you rarely annotate generics by hand — and the Register interface lets you set app-wide defaults for the error, meta, and key types instead of repeating them per call.

Key Concepts

  • Type inference by default: data's type is inferred from queryFn's return type; select narrows it further. Type it by giving queryFn a real return type, not by annotating useQuery generics.
  • Discriminated union result: the query result is a discriminated union keyed by status/derived booleans (isSuccess, etc.) — narrowing on isSuccess makes data non-undefined without a manual assertion.
  • Default error type is Error: override per-call via generics (loses inference for the rest), or globally via declare module '@tanstack/react-query' { interface Register { defaultError: MyError } }.
  • Register interface: the mechanism for registering app-wide defaultError, queryMeta/mutationMeta (must extend Record<string, unknown>), and queryKey/mutationKey (must extend Array) types once, applied everywhere.
  • queryOptions/mutationOptions helpers: extracting options into a shared function loses inference unless wrapped in these helpers — they also make the returned queryKey "know" its associated data type, so queryClient.getQueryData(opts.queryKey) comes back typed.
  • Support window: follows DefinitelyTyped's window (roughly the last 2 years of TS); type changes ship as semver-patch, so pin a patch version if strict type stability matters more than picking up type fixes.

Code Examples

// Global error type — forces explicit narrowing at every call site
declare module '@tanstack/react-query' {
  interface Register {
    defaultError: unknown
  }
}

// queryOptions preserves inference when extracted into a shared function
function groupOptions() {
  return queryOptions({ queryKey: ['groups'], queryFn: fetchGroups, staleTime: 5000 })
}
useQuery(groupOptions())
const data = queryClient.getQueryData(groupOptions().queryKey) // typed, not unknown
  • What it demonstrates: registering a stricter global error type, and preserving type inference when query options are shared across useQuery and direct queryClient calls.

Key Takeaways

  1. Don't fight inference by hand-annotating useQuery generics — fix the queryFn's return type instead, inference flows from there.
  2. Use queryOptions/mutationOptions the moment options are extracted out of the component that calls useQuery/useMutation — it's the difference between a typed queryKey and an unknown one at every other call site.
  3. Register is the one-time, app-wide way to set error/meta/key types; reach for per-call generics only for a genuine one-off exception.

Connects To

  • Query Options / Mutations: where queryOptions/mutationOptions are used day to day.
  • Disabling/Pausing Queries: skipToken, the type-safe way to disable a query conditionally.