@jgamaraalv/ts-dev-kit 2.1.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,376 @@
1
+ # TanStack Query v5 — Advanced Patterns
2
+
3
+ ## Table of Contents
4
+ - [TypeScript](#typescript)
5
+ - [Suspense](#suspense)
6
+ - [Dependent Queries](#dependent-queries)
7
+ - [Disabling Queries](#disabling-queries)
8
+ - [Network Modes](#network-modes)
9
+ - [Request Waterfalls](#request-waterfalls)
10
+ - [Default Query Function](#default-query-function)
11
+ - [Window Focus Refetching](#window-focus-refetching)
12
+ - [Placeholder Data](#placeholder-data)
13
+
14
+ ## TypeScript
15
+
16
+ Requires TypeScript v4.7+. Type changes ship as **patch** semver.
17
+
18
+ ### Type Inference
19
+
20
+ Types flow from `queryFn` automatically — do not pass generics unless necessary:
21
+
22
+ ```tsx
23
+ // GOOD — types inferred
24
+ const { data } = useQuery({
25
+ queryKey: ['todos'],
26
+ queryFn: () => fetchTodos(), // returns Promise<Todo[]>
27
+ })
28
+ // data: Todo[] | undefined
29
+
30
+ // BAD — explicit generics break inference for other params
31
+ const { data } = useQuery<Todo[], Error>({ ... })
32
+ ```
33
+
34
+ ### Type Narrowing (discriminated union)
35
+
36
+ ```tsx
37
+ const { data, isSuccess, error, isError } = useQuery({ ... })
38
+
39
+ if (isSuccess) {
40
+ data // Todo[] (not undefined)
41
+ }
42
+ if (isError) {
43
+ error // Error (not null)
44
+ }
45
+ ```
46
+
47
+ ### Custom Error Types
48
+
49
+ Prefer type narrowing over generics:
50
+
51
+ ```tsx
52
+ if (axios.isAxiosError(error)) {
53
+ error // AxiosError
54
+ }
55
+ ```
56
+
57
+ ### Register Interface — global type overrides
58
+
59
+ ```tsx
60
+ declare module '@tanstack/react-query' {
61
+ interface Register {
62
+ defaultError: AxiosError // all errors typed as AxiosError
63
+ queryMeta: { auth?: boolean } // must extend Record<string, unknown>
64
+ mutationMeta: { auth?: boolean }
65
+ }
66
+ }
67
+ ```
68
+
69
+ ### queryOptions / mutationOptions for type flow
70
+
71
+ ```tsx
72
+ import { queryOptions, mutationOptions } from '@tanstack/react-query'
73
+
74
+ const todosOpts = (filters: Filters) => queryOptions({
75
+ queryKey: ['todos', filters],
76
+ queryFn: () => fetchTodos(filters),
77
+ })
78
+
79
+ // Types flow to getQueryData:
80
+ const data = queryClient.getQueryData(todosOpts({ status: 'done' }).queryKey)
81
+ // ^? Todo[] | undefined
82
+
83
+ const addTodoOpts = () => mutationOptions({
84
+ mutationKey: ['addTodo'],
85
+ mutationFn: (input: CreateTodo) => createTodo(input),
86
+ })
87
+ ```
88
+
89
+ ### skipToken — type-safe conditional queries
90
+
91
+ ```tsx
92
+ import { skipToken, useQuery } from '@tanstack/react-query'
93
+
94
+ const { data } = useQuery({
95
+ queryKey: ['user', userId],
96
+ queryFn: userId ? () => fetchUser(userId) : skipToken,
97
+ })
98
+ // queryFn type is correctly inferred without worrying about undefined userId
99
+ ```
100
+
101
+ ## Suspense
102
+
103
+ ### Dedicated Hooks
104
+
105
+ | Hook | Purpose |
106
+ |------|---------|
107
+ | `useSuspenseQuery` | Suspends until data ready; `data` guaranteed defined |
108
+ | `useSuspenseInfiniteQuery` | Same for infinite queries |
109
+ | `useSuspenseQueries` | Parallel suspense queries (avoids serial waterfall) |
110
+
111
+ ### Key differences from useQuery
112
+
113
+ - No `enabled` option — cannot conditionally disable
114
+ - No `placeholderData` — use `startTransition` to avoid fallback on key change
115
+ - `data` is always defined (never `undefined`)
116
+ - Multiple suspense queries in same component fetch **serially** — use `useSuspenseQueries` for parallel
117
+
118
+ ### Parallel suspense queries
119
+
120
+ ```tsx
121
+ // WRONG — serial (Article suspends, then Comments fetches after)
122
+ function Page({ id }: { id: string }) {
123
+ const { data: article } = useSuspenseQuery(articleOptions(id))
124
+ const { data: comments } = useSuspenseQuery(commentsOptions(id))
125
+ }
126
+
127
+ // CORRECT — parallel
128
+ function Page({ id }: { id: string }) {
129
+ const [{ data: article }, { data: comments }] = useSuspenseQueries({
130
+ queries: [articleOptions(id), commentsOptions(id)],
131
+ })
132
+ }
133
+ ```
134
+
135
+ ### Error boundary reset
136
+
137
+ ```tsx
138
+ import { QueryErrorResetBoundary } from '@tanstack/react-query'
139
+ import { ErrorBoundary } from 'react-error-boundary'
140
+
141
+ function App() {
142
+ return (
143
+ <QueryErrorResetBoundary>
144
+ {({ reset }) => (
145
+ <ErrorBoundary onReset={reset} fallbackRender={({ resetErrorBoundary }) => (
146
+ <div>
147
+ <p>Something went wrong</p>
148
+ <button onClick={() => resetErrorBoundary()}>Try again</button>
149
+ </div>
150
+ )}>
151
+ <Page />
152
+ </ErrorBoundary>
153
+ )}
154
+ </QueryErrorResetBoundary>
155
+ )
156
+ }
157
+ ```
158
+
159
+ ### throwOnError default for suspense
160
+
161
+ ```tsx
162
+ // Only throws when there's NO cached data
163
+ throwOnError: (error, query) => typeof query.state.data === 'undefined'
164
+ ```
165
+
166
+ To throw all errors: manually throw when `error && !isFetching`.
167
+
168
+ ## Dependent Queries
169
+
170
+ Use `enabled` to chain queries:
171
+
172
+ ```tsx
173
+ const { data: user } = useQuery({
174
+ queryKey: ['user', email],
175
+ queryFn: () => getUserByEmail(email),
176
+ })
177
+
178
+ const { data: projects } = useQuery({
179
+ queryKey: ['projects', user?.id],
180
+ queryFn: () => getProjectsByUser(user!.id),
181
+ enabled: !!user?.id,
182
+ })
183
+ ```
184
+
185
+ **Dynamic parallel dependent queries:**
186
+
187
+ ```tsx
188
+ const { data: userIds } = useQuery({
189
+ queryKey: ['users'],
190
+ queryFn: getUsersData,
191
+ select: (users) => users.map(u => u.id),
192
+ })
193
+
194
+ const usersMessages = useQueries({
195
+ queries: userIds
196
+ ? userIds.map((id) => ({
197
+ queryKey: ['messages', id],
198
+ queryFn: () => getMessagesByUser(id),
199
+ }))
200
+ : [],
201
+ })
202
+ ```
203
+
204
+ ## Disabling Queries
205
+
206
+ ### enabled: false
207
+
208
+ - No auto-fetch on mount, no background refetch
209
+ - Ignores `invalidateQueries` and `refetchQueries`
210
+ - Manual `refetch()` still works
211
+ - Without cache: starts `status: 'pending'`, `fetchStatus: 'idle'`
212
+
213
+ ### skipToken (preferred for TypeScript)
214
+
215
+ ```tsx
216
+ const { data } = useQuery({
217
+ queryKey: ['todos', filter],
218
+ queryFn: filter ? () => fetchTodos(filter) : skipToken,
219
+ })
220
+ ```
221
+
222
+ **skipToken does NOT support `refetch()`** — throws "Missing queryFn". Use `enabled: false` if manual refetch is needed.
223
+
224
+ ### Lazy queries (preferred pattern)
225
+
226
+ ```tsx
227
+ const [enabled, setEnabled] = useState(false)
228
+ const { data } = useQuery({
229
+ queryKey: ['todos'],
230
+ queryFn: fetchTodos,
231
+ enabled,
232
+ })
233
+ <button onClick={() => setEnabled(true)}>Load</button>
234
+ ```
235
+
236
+ ### isLoading vs isPending
237
+
238
+ - `isPending` = no cached data (regardless of fetch status)
239
+ - `isLoading` = `isPending && isFetching` (first actual load)
240
+
241
+ Use `isLoading` to distinguish "first load" from "disabled/idle".
242
+
243
+ ## Network Modes
244
+
245
+ `networkMode: 'online' | 'always' | 'offlineFirst'` (default: `'online'`)
246
+
247
+ | Mode | Behavior | Use Case |
248
+ |------|----------|----------|
249
+ | `online` | Only fetches with network; pauses retries offline | Default for most APIs |
250
+ | `always` | Ignores online/offline; never pauses | AsyncStorage, local data, `Promise.resolve` |
251
+ | `offlineFirst` | Tries once, pauses retries if offline | Service worker / HTTP cache (PWAs) |
252
+
253
+ **Key:** With `online` mode, check both `status` and `fetchStatus` before showing spinners — a query can be `pending` + `paused` (no data, no network).
254
+
255
+ ## Request Waterfalls
256
+
257
+ Each waterfall step = at least one server roundtrip. At 250ms latency (3G), 4 steps = 1000ms overhead.
258
+
259
+ ### Three types
260
+
261
+ **1. Serial queries in one component:**
262
+ ```tsx
263
+ // BAD — waterfall
264
+ const { data: user } = useQuery(userOptions(email))
265
+ const { data: projects } = useQuery({ ...projectsOptions(user?.id), enabled: !!user?.id })
266
+
267
+ // FIX — restructure API to avoid dependency
268
+ const { data: projects } = useQuery(projectsByEmailOptions(email))
269
+ ```
270
+
271
+ **2. Nested component waterfalls:**
272
+ Parent suspends → child mounts → child fetches. Fix: hoist child query up or prefetch at route level.
273
+
274
+ **3. Code splitting + queries:**
275
+ `Markup → JS → Lazy chunk → Query` = quadruple waterfall. Fix: prefetch at route level.
276
+
277
+ ### Fix with useSuspenseQueries
278
+
279
+ ```tsx
280
+ // BAD — serial (each suspends before next mounts)
281
+ const { data: a } = useSuspenseQuery(optionsA)
282
+ const { data: b } = useSuspenseQuery(optionsB)
283
+
284
+ // GOOD — parallel
285
+ const [{ data: a }, { data: b }] = useSuspenseQueries({
286
+ queries: [optionsA, optionsB],
287
+ })
288
+ ```
289
+
290
+ ### Fix with prefetching
291
+
292
+ ```tsx
293
+ // In route loader or parent component:
294
+ queryClient.prefetchQuery(articleOptions(id))
295
+ queryClient.prefetchQuery(commentsOptions(id))
296
+ // Both start fetching immediately, no waterfall
297
+ ```
298
+
299
+ ## Default Query Function
300
+
301
+ Share one `queryFn` across all queries:
302
+
303
+ ```tsx
304
+ const defaultQueryFn = async ({ queryKey }: { queryKey: QueryKey }) => {
305
+ const { data } = await axios.get(`https://api.example.com${queryKey[0]}`)
306
+ return data
307
+ }
308
+
309
+ const queryClient = new QueryClient({
310
+ defaultOptions: { queries: { queryFn: defaultQueryFn } },
311
+ })
312
+
313
+ // Usage — no queryFn needed:
314
+ useQuery({ queryKey: ['/posts'] })
315
+ useQuery({ queryKey: [`/posts/${postId}`], enabled: !!postId })
316
+ ```
317
+
318
+ Per-query `queryFn` overrides the default.
319
+
320
+ ## Window Focus Refetching
321
+
322
+ Enabled by default. Stale queries refetch when the browser tab regains focus.
323
+
324
+ ```tsx
325
+ // Disable globally:
326
+ new QueryClient({
327
+ defaultOptions: { queries: { refetchOnWindowFocus: false } },
328
+ })
329
+
330
+ // Disable per query:
331
+ useQuery({ queryKey: ['todos'], queryFn: fetchTodos, refetchOnWindowFocus: false })
332
+ ```
333
+
334
+ ### React Native
335
+
336
+ ```tsx
337
+ import { AppState, Platform } from 'react-native'
338
+ import { focusManager } from '@tanstack/react-query'
339
+
340
+ function onAppStateChange(status: AppStateStatus) {
341
+ if (Platform.OS !== 'web') {
342
+ focusManager.setFocused(status === 'active')
343
+ }
344
+ }
345
+
346
+ useEffect(() => {
347
+ const sub = AppState.addEventListener('change', onAppStateChange)
348
+ return () => sub.remove()
349
+ }, [])
350
+ ```
351
+
352
+ ## Placeholder Data
353
+
354
+ Temporary display data that is NOT persisted to cache (unlike `initialData`).
355
+
356
+ ```tsx
357
+ // Static placeholder
358
+ useQuery({ queryKey: ['todos'], queryFn: fetchTodos, placeholderData: [] })
359
+
360
+ // Keep previous data during key changes (pagination)
361
+ useQuery({
362
+ queryKey: ['todos', id],
363
+ queryFn: () => fetchTodo(id),
364
+ placeholderData: (previousData) => previousData,
365
+ })
366
+
367
+ // From another query's cache (list → detail preview)
368
+ useQuery({
369
+ queryKey: ['todo', todoId],
370
+ queryFn: () => fetchTodo(todoId),
371
+ placeholderData: () =>
372
+ queryClient.getQueryData<Todo[]>(['todos'])?.find(t => t.id === todoId),
373
+ })
374
+ ```
375
+
376
+ `isPlaceholderData` is `true` when showing placeholder data.
@@ -0,0 +1,297 @@
1
+ # TanStack Query v5 — API Reference
2
+
3
+ ## Table of Contents
4
+ - [useQuery Options](#usequery-options)
5
+ - [useQuery Returns](#usequery-returns)
6
+ - [useMutation Options](#usemutation-options)
7
+ - [useMutation Returns](#usemutation-returns)
8
+ - [useInfiniteQuery Options](#useinfinitequery-options)
9
+ - [useInfiniteQuery Returns](#useinfinitequery-returns)
10
+ - [QueryClient Methods](#queryclient-methods)
11
+ - [QueryCache](#querycache)
12
+ - [MutationCache](#mutationcache)
13
+ - [queryOptions / infiniteQueryOptions](#queryoptions--infinitequeryoptions)
14
+
15
+ ## useQuery Options
16
+
17
+ ```tsx
18
+ const result = useQuery(options, queryClient?)
19
+ ```
20
+
21
+ | Option | Type | Default |
22
+ |--------|------|---------|
23
+ | `queryKey` | `unknown[]` | **required** |
24
+ | `queryFn` | `(context: QueryFunctionContext) => Promise<TData>` | **required** (unless default set) |
25
+ | `enabled` | `boolean \| (query: Query) => boolean` | `true` |
26
+ | `staleTime` | `number \| 'static' \| ((query) => number \| 'static')` | `0` |
27
+ | `gcTime` | `number \| Infinity` | `300000` (5 min) / `Infinity` on server |
28
+ | `retry` | `boolean \| number \| (failureCount, error) => boolean` | `3` client / `0` server |
29
+ | `retryDelay` | `number \| (attempt, error) => number` | exponential backoff |
30
+ | `retryOnMount` | `boolean` | `true` |
31
+ | `refetchInterval` | `number \| false \| ((query) => number \| false)` | — |
32
+ | `refetchIntervalInBackground` | `boolean` | `false` |
33
+ | `refetchOnMount` | `boolean \| 'always' \| ((query) => boolean \| 'always')` | `true` |
34
+ | `refetchOnWindowFocus` | `boolean \| 'always' \| ((query) => boolean \| 'always')` | `true` |
35
+ | `refetchOnReconnect` | `boolean \| 'always' \| ((query) => boolean \| 'always')` | `true` |
36
+ | `networkMode` | `'online' \| 'always' \| 'offlineFirst'` | `'online'` |
37
+ | `select` | `(data: TData) => unknown` | — |
38
+ | `initialData` | `TData \| () => TData` | — |
39
+ | `initialDataUpdatedAt` | `number \| (() => number)` | — |
40
+ | `placeholderData` | `TData \| (previousValue, previousQuery) => TData` | — |
41
+ | `notifyOnChangeProps` | `string[] \| 'all'` | tracked access |
42
+ | `structuralSharing` | `boolean \| (oldData, newData) => unknown` | `true` |
43
+ | `throwOnError` | `boolean \| (error, query) => boolean` | — |
44
+ | `meta` | `Record<string, unknown>` | — |
45
+ | `subscribed` | `boolean` | `true` |
46
+
47
+ **QueryFunctionContext** passed to `queryFn`:
48
+ - `queryKey` — the query key array
49
+ - `client` — the QueryClient instance
50
+ - `signal` — AbortSignal for cancellation
51
+ - `meta` — optional metadata from the query option
52
+
53
+ **Key notes:**
54
+ - `queryFn` must resolve data; data **cannot be `undefined`**
55
+ - `initialData` persists to cache; `placeholderData` does not
56
+ - `select` only re-runs when data or function reference changes — wrap in `useCallback` if inline
57
+ - `staleTime: 'static'` — data is never stale, even `refetchOnWindowFocus: 'always'` is blocked
58
+
59
+ ## useQuery Returns
60
+
61
+ | Property | Type | Notes |
62
+ |----------|------|-------|
63
+ | `data` | `TData \| undefined` | |
64
+ | `error` | `TError \| null` | |
65
+ | `status` | `'pending' \| 'error' \| 'success'` | |
66
+ | `isPending` | `boolean` | No cached data |
67
+ | `isSuccess` | `boolean` | |
68
+ | `isError` | `boolean` | |
69
+ | `isLoading` | `boolean` | `isPending && isFetching` (first load) |
70
+ | `isLoadingError` | `boolean` | Failed on first fetch |
71
+ | `isRefetchError` | `boolean` | Failed while refetching |
72
+ | `fetchStatus` | `'fetching' \| 'paused' \| 'idle'` | |
73
+ | `isFetching` | `boolean` | queryFn running |
74
+ | `isPaused` | `boolean` | Wants to fetch but offline |
75
+ | `isRefetching` | `boolean` | `isFetching && !isPending` |
76
+ | `isStale` | `boolean` | |
77
+ | `isPlaceholderData` | `boolean` | |
78
+ | `isFetched` | `boolean` | |
79
+ | `isFetchedAfterMount` | `boolean` | |
80
+ | `dataUpdatedAt` | `number` | Timestamp |
81
+ | `errorUpdatedAt` | `number` | |
82
+ | `failureCount` | `number` | Resets to 0 on success |
83
+ | `failureReason` | `TError \| null` | |
84
+ | `errorUpdateCount` | `number` | Total errors ever |
85
+ | `isEnabled` | `boolean` | |
86
+ | `refetch` | `(opts?) => Promise<UseQueryResult>` | `cancelRefetch` defaults to `true` |
87
+
88
+ ## useMutation Options
89
+
90
+ ```tsx
91
+ const result = useMutation(options, queryClient?)
92
+ ```
93
+
94
+ | Option | Type | Default |
95
+ |--------|------|---------|
96
+ | `mutationFn` | `(variables, context: MutationFunctionContext) => Promise<TData>` | **required** |
97
+ | `mutationKey` | `unknown[]` | — |
98
+ | `gcTime` | `number \| Infinity` | — |
99
+ | `retry` | `boolean \| number \| (failureCount, error) => boolean` | `0` |
100
+ | `retryDelay` | `number \| (attempt, error) => number` | — |
101
+ | `networkMode` | `'online' \| 'always' \| 'offlineFirst'` | `'online'` |
102
+ | `onMutate` | `(variables, context) => Promise<TOnMutateResult> \| TOnMutateResult` | — |
103
+ | `onSuccess` | `(data, variables, onMutateResult, context) => Promise \| void` | — |
104
+ | `onError` | `(error, variables, onMutateResult, context) => Promise \| void` | — |
105
+ | `onSettled` | `(data, error, variables, onMutateResult, context) => Promise \| void` | — |
106
+ | `scope` | `{ id: string }` | unique (parallel) |
107
+ | `throwOnError` | `boolean \| (error) => boolean` | — |
108
+ | `meta` | `Record<string, unknown>` | — |
109
+
110
+ **Callback context:** `onMutate` return value is passed as `onMutateResult` to `onSuccess`/`onError`/`onSettled`. The `context` object provides `context.client` (the QueryClient).
111
+
112
+ **Scope:** Mutations with the same `scope.id` run **serially** (queued), not in parallel.
113
+
114
+ ## useMutation Returns
115
+
116
+ | Property | Type | Notes |
117
+ |----------|------|-------|
118
+ | `mutate` | `(variables, { onSuccess?, onError?, onSettled? }) => void` | Fire and forget |
119
+ | `mutateAsync` | `(variables, opts?) => Promise<TData>` | Returns promise |
120
+ | `status` | `'idle' \| 'pending' \| 'error' \| 'success'` | |
121
+ | `isIdle`, `isPending`, `isSuccess`, `isError` | `boolean` | |
122
+ | `isPaused` | `boolean` | |
123
+ | `data` | `TData \| undefined` | |
124
+ | `error` | `TError \| null` | |
125
+ | `variables` | `TVariables \| undefined` | Last call's variables |
126
+ | `reset` | `() => void` | Clear mutation state |
127
+ | `submittedAt` | `number` | |
128
+ | `failureCount` | `number` | |
129
+ | `failureReason` | `TError \| null` | |
130
+
131
+ ## useInfiniteQuery Options
132
+
133
+ Inherits all `useQuery` options plus:
134
+
135
+ | Option | Type | Default |
136
+ |--------|------|---------|
137
+ | `initialPageParam` | `TPageParam` | **required** |
138
+ | `getNextPageParam` | `(lastPage, allPages, lastPageParam, allPageParams) => TPageParam \| undefined \| null` | **required** |
139
+ | `getPreviousPageParam` | `(firstPage, allPages, firstPageParam, allPageParams) => TPageParam \| undefined \| null` | — |
140
+ | `maxPages` | `number` | `undefined` (unlimited) |
141
+
142
+ Return `undefined`/`null` from page param getters to signal no more pages. When `maxPages` is set, both `getNextPageParam` and `getPreviousPageParam` must be defined.
143
+
144
+ ## useInfiniteQuery Returns
145
+
146
+ Inherits all `useQuery` returns plus:
147
+
148
+ | Property | Type | Notes |
149
+ |----------|------|-------|
150
+ | `data.pages` | `TData[]` | Array of page results |
151
+ | `data.pageParams` | `unknown[]` | Array of page params used |
152
+ | `fetchNextPage` | `(opts?) => Promise` | |
153
+ | `fetchPreviousPage` | `(opts?) => Promise` | |
154
+ | `hasNextPage` | `boolean` | |
155
+ | `hasPreviousPage` | `boolean` | |
156
+ | `isFetchingNextPage` | `boolean` | |
157
+ | `isFetchingPreviousPage` | `boolean` | |
158
+ | `isFetchNextPageError` | `boolean` | |
159
+ | `isFetchPreviousPageError` | `boolean` | |
160
+
161
+ ## QueryClient Methods
162
+
163
+ ### Constructor
164
+
165
+ ```tsx
166
+ const queryClient = new QueryClient({
167
+ queryCache?: QueryCache,
168
+ mutationCache?: MutationCache,
169
+ defaultOptions?: {
170
+ queries?: QueryOptions,
171
+ mutations?: MutationOptions,
172
+ hydrate?: HydrateOptions,
173
+ dehydrate?: DehydrateOptions,
174
+ },
175
+ })
176
+ ```
177
+
178
+ ### Data Access (synchronous)
179
+
180
+ | Method | Signature | Notes |
181
+ |--------|-----------|-------|
182
+ | `getQueryData` | `(queryKey) => TData \| undefined` | |
183
+ | `getQueriesData` | `(filters) => [QueryKey, TData \| undefined][]` | |
184
+ | `getQueryState` | `(queryKey) => QueryState \| undefined` | |
185
+ | `setQueryData` | `(queryKey, updater: TData \| (old => TData)) => TData \| undefined` | **Must be immutable** |
186
+ | `setQueriesData` | `(filters, updater) => [QueryKey, TData \| undefined][]` | Only updates existing entries |
187
+
188
+ ### Fetching (async)
189
+
190
+ | Method | Signature | Notes |
191
+ |--------|-----------|-------|
192
+ | `fetchQuery` | `(options) => Promise<TData>` | Returns cached data if fresh; throws on error |
193
+ | `prefetchQuery` | `(options) => Promise<void>` | Never throws; never returns data |
194
+ | `ensureQueryData` | `(options) => Promise<TData>` | Only fetches if not cached; `revalidateIfStale` option |
195
+ | `fetchInfiniteQuery` | `(options) => Promise<InfiniteData>` | |
196
+ | `prefetchInfiniteQuery` | `(options) => Promise<void>` | |
197
+ | `ensureInfiniteQueryData` | `(options) => Promise<InfiniteData>` | |
198
+
199
+ ### Cache Management
200
+
201
+ | Method | Signature | Notes |
202
+ |--------|-----------|-------|
203
+ | `invalidateQueries` | `(filters?, options?) => Promise<void>` | Marks stale + refetches active |
204
+ | `refetchQueries` | `(filters?, options?) => Promise<void>` | |
205
+ | `cancelQueries` | `(filters?) => Promise<void>` | |
206
+ | `removeQueries` | `(filters?) => void` | |
207
+ | `resetQueries` | `(filters?, options?) => Promise<void>` | Resets to initial state |
208
+ | `clear` | `() => void` | Clears all caches |
209
+
210
+ ### invalidateQueries filters
211
+
212
+ ```tsx
213
+ queryClient.invalidateQueries({
214
+ queryKey?: QueryKey, // prefix match by default
215
+ exact?: boolean, // exact key match
216
+ refetchType?: 'active' | 'inactive' | 'all' | 'none', // default: 'active'
217
+ predicate?: (query: Query) => boolean,
218
+ })
219
+ ```
220
+
221
+ ### Defaults
222
+
223
+ | Method | Signature |
224
+ |--------|-----------|
225
+ | `setQueryDefaults` | `(queryKey, options) => void` |
226
+ | `getQueryDefaults` | `(queryKey) => QueryOptions` |
227
+ | `setMutationDefaults` | `(mutationKey, options) => void` |
228
+ | `getMutationDefaults` | `(mutationKey) => MutationOptions` |
229
+ | `setDefaultOptions` | `(options) => void` |
230
+
231
+ `setQueryDefaults` order matters: register from most generic to least generic key.
232
+
233
+ ### Status
234
+
235
+ | Method | Returns |
236
+ |--------|---------|
237
+ | `isFetching(filters?)` | `number` — count of fetching queries |
238
+ | `isMutating(filters?)` | `number` — count of in-flight mutations |
239
+
240
+ ## QueryCache
241
+
242
+ ```tsx
243
+ const queryCache = new QueryCache({
244
+ onError?: (error, query) => void,
245
+ onSuccess?: (data, query) => void,
246
+ onSettled?: (data, error, query) => void,
247
+ })
248
+ ```
249
+
250
+ | Method | Returns |
251
+ |--------|---------|
252
+ | `queryCache.find(filters)` | `Query \| undefined` |
253
+ | `queryCache.findAll(filters?)` | `Query[]` |
254
+ | `queryCache.subscribe(callback)` | `() => void` (unsubscribe) |
255
+ | `queryCache.clear()` | `void` |
256
+
257
+ ## MutationCache
258
+
259
+ ```tsx
260
+ const mutationCache = new MutationCache({
261
+ onMutate?: (variables, mutation, context) => void,
262
+ onError?: (error, variables, onMutateResult, mutation, context) => void,
263
+ onSuccess?: (data, variables, onMutateResult, mutation, context) => void,
264
+ onSettled?: (data, error, variables, onMutateResult, mutation, context) => void,
265
+ })
266
+ ```
267
+
268
+ Global MutationCache callbacks **always fire** and cannot be overridden by individual mutations. If callbacks return a Promise, it is awaited.
269
+
270
+ | Method | Returns |
271
+ |--------|---------|
272
+ | `mutationCache.getAll()` | `Mutation[]` |
273
+ | `mutationCache.subscribe(callback)` | `() => void` |
274
+ | `mutationCache.clear()` | `void` |
275
+
276
+ ## queryOptions / infiniteQueryOptions
277
+
278
+ ```tsx
279
+ import { queryOptions, infiniteQueryOptions } from '@tanstack/react-query'
280
+
281
+ // queryOptions — preserves type inference across useQuery, prefetchQuery, getQueryData
282
+ const opts = queryOptions({
283
+ queryKey: ['groups', id],
284
+ queryFn: () => fetchGroups(id),
285
+ staleTime: 5000,
286
+ })
287
+
288
+ // infiniteQueryOptions — same for infinite queries
289
+ const infOpts = infiniteQueryOptions({
290
+ queryKey: ['projects'],
291
+ queryFn: ({ pageParam }) => fetchProjects(pageParam),
292
+ initialPageParam: 0,
293
+ getNextPageParam: (lastPage) => lastPage.nextCursor,
294
+ })
295
+ ```
296
+
297
+ Without `queryOptions`, `getQueryData` returns `unknown`. With it, types flow automatically.