Skip to content

Commit aee932b

Browse files
authored
feat(types): TError can now globally be defaulted / registered (#4903)
* types: TError can now globally be defaulted / registered * docs: add DefaultError * test: stabilize a flaky test
1 parent af7e087 commit aee932b

36 files changed

+318
-184
lines changed

docs/react/guides/migrating-to-v5.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ const queryClient = new QueryClient({
167167

168168
To make the `useErrorBoundary` option more framework-agnostic and avoid confusion with the established React function prefix "`use`" for hooks and the "ErrorBoundary" component name, it has been renamed to `throwErrors` to more accurately reflect its functionality.
169169

170-
### `Error` is now the default type for errors instead of `unknown`
170+
### TypeScript: `Error` is now the default type for errors instead of `unknown`
171171

172172
Even though in JavaScript, you can `throw` anything (which makes `unknown` the most correct type), almost always, `Errors` (or subclasses of `Error`) are thrown. This change makes it easier to work with the `error` field in TypeScript for most cases.
173173

@@ -185,6 +185,8 @@ useQuery<number, string>({
185185
})
186186
```
187187

188+
For a way to set a different kind of Error globally, see [the TypeScript Guide](../typescript#registering-a-global-error).
189+
188190
### eslint `prefer-query-object-syntax` rule is removed
189191

190192
Since the only supported syntax now is the object syntax, this rule is no longer needed

docs/react/typescript.md

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -85,34 +85,59 @@ if (isSuccess) {
8585

8686
## Typing the error field
8787

88-
The type for error defaults to `unknown`. This is in line with what TypeScript gives you per default in a catch clauses (see [useUnknownInCatchVariables](https://devblogs.microsoft.com/typescript/announcing-typescript-4-4/#use-unknown-catch-variables)). The safest way to work with `error` would be to perform a runtime check; another way would be to explicitly define types for `data` and `error`:
88+
The type for error defaults to `Error`, because that is what most users expect.
8989

9090
```tsx
9191
const { error } = useQuery({ queryKey: ['groups'], queryFn: fetchGroups })
92-
// ^? const error: unknown
93-
94-
if (error instanceof Error) {
95-
error
96-
// ^? const error: Error
97-
}
92+
// ^? const error: Error
9893
```
9994

10095
[//]: # 'Playground5'
10196

102-
[typescript playground](https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAbzgVwM4FMCKz1QJ5wC+cAZlBCHAORToCGAxjALQCOO+VAsAFCiSw4dAB7AIqUuUpURY1Nx68YeMOjgBxcsjBwAvIjjAAJgC44AO2QgARriK9eDCOdTwS6GAwAWmiNon6ABQAlGYAClLAGAA8vtoA2gC6AHx6qbLiAHQA5h6BVAD02Vpg8sGZMF7o5oG0qJAuarqpdQ0YmUZ0MHTBDjxOLvBIuORQRHooGNi4eIHxVMV+pVSJADSkHt5xpb08BQVwh0cAegD8fcAkcIEj0IaDdOYM6BBXAKJQo8GIvIe3ULx9nAzrxCEA)
97+
[typescript playground](https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAbzgVwM4FMCKz1QJ5wC+cAZlBCHAOQACMAhgHaoMDGA1gPRTr2swBaAI458VALAAoUJFhx6AD2ARUpcpSqLlqCZKkw8YdHADi5ZGDgBeRHGAATAFxxGyEACNcRKVNYRm8CToMKwAFmYQFqo2ABQAlM4ACurAGAA8ERYA2gC6AHzWBVoqAHQA5sExVJxl5mA6cSUwoeiMMTyokMzGVgUdXRgl9vQMcT6SfgG2uORQRNYoGNi4eDFZVLWR9VQ5ADSkwWGZ9WOSnJxwl1cAegD8QA)
10398

10499
[//]: # 'Playground5'
105100

101+
If you want to throw a custom error, or something that isn't an Error at all, you can specify the type of the error field:
102+
106103
```tsx
107-
const { error } = useQuery<Group[], Error>(['groups'], fetchGroups)
104+
const { error } = useQuery<Group[], string>(['groups'], fetchGroups)
105+
// ^? const error: string | null
106+
```
107+
108+
However, this has the drawback that type inference for all other generics of `useQuery` will not work anymore. It is generally not considered a good practice to throw something that isn't and `Error`, so if you have a subclass like `AxisError` you can use _type narrowing_ to make the error field more specific:
109+
110+
```tsx
111+
import axios from 'axios'
112+
113+
const { error } = useQuery({ queryKey: ['groups'], queryFn: fetchGroups })
108114
// ^? const error: Error | null
115+
116+
if (axios.isAxiosError(error)) {
117+
error
118+
// ^? const error: AxiosError
119+
}
109120
```
110121

122+
[//]: # 'Playground6'
123+
[typescript playground](https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAbzgVwM4FMCKz1QJ5wC+cAZlBCHAOQACMAhgHaoMDGA1gPRTr2swBaAI458VALAAoUJFhx6AD2ARUpcpSqLlqCZKkw8YdHADi5ZGDgBeRHGAATAFxxGyEACNcRKVNYRm8CToMKwAFmYQFqo2ABQAlM4ACurAGAA8ERYA2gC6AHzWBVoqAHQA5sExVJxl5mA6cSUwoeiMMTyokMzGVgUdXRgl9vQMcT6SfgG2uORQRNYoGNi4eDFIIisA0uh4zllUtZH1VDkANHAb+ABijM5BIeF1qoRjkpyccJ9fAHoA-OPAEhwGLFVAlVIAQSUKgAolBZjEZtA4nFEFJPkioOi4O84H8pIQgA)
111124
[//]: # 'Playground6'
112125

113-
[typescript playground](https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAbzgVwM4FMCKz1QJ5wC+cAZlBCHAORToCGAxjALQCOO+VAsAFCiSw4dAB7AIqUuUpURY1Nx68YeMOjgBxcsjBwAvIjjAAJgC44AO2QgARriK9eDCOdTwS6GAwAWmiNon6ABQAlGYAClLAGAA8vtoA2gC6AHx6qbLiAHQA5h6BVAD02Vpg8sGZMF7o5oG0qJAuarqpdQ0YmUZ0MHTBDjxOLvBIuORQRHooGNi4eLElSQA0cACiUKPJgfFUxX6lVIlL7p4+Jai9PAUFcNc3AHoA-LxAA)
126+
### Registering a global Error
127+
128+
TanStack Query v5 allows for a way to set a global Error type for everything, without having to specify generics on call-sides, by amending the `Register` interface. This will make sure inference still works, but the error field will be of the specified type:
129+
130+
```tsx
131+
declare module '@tanstack/react-query' {
132+
interface Register {
133+
defaultError: AxiosError
134+
}
135+
}
136+
137+
const { error } = useQuery({ queryKey: ['groups'], queryFn: fetchGroups })
138+
// ^? const error: AxiosError | null
139+
```
114140

115-
[//]: # 'Playground6'
116141
[//]: # 'Materials'
117142

118143
## Further Reading

docs/vue/typescript.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ replace:
3333
[//]: # 'Playground5'
3434
[//]: # 'Playground6'
3535

36-
[typescript playground](https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAbzgVwM4FMCKz1QJ5wC+cAZlBCHAOQACMAhgHaoMDGA1gPQBuOAtAEcc+KgFgAUKEiw49AB7AIqUuUpV5i1GPESYeMOjgBxcsjBwAvIjjAAJgC44jZCABGuIhImsIzeCXQYVgALEwgzZSsACgBKRwAFVWAMAB4wswBtAF0APks8jSUAOgBzQKiqThLTMC0Yophg9EYoqHRUSGZDCzy2jt8MItt6BhivcR8-a1xyKCJLFAxsXDw0muyAGjgAUShZnKiMqmrw2qosrYCg0JrUMfFOTjhnl4A9AH4JIA)
36+
[typescript playground](https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAbzgVwM4FMCKz1QJ5wC+cAZlBCHAOQACMAhgHaoMDGA1gPRTr2swBaAI458VALAAoUJFhx6AD2ARUpcpSqLlqCZKkw8YdHADi5ZGDgBeRHGAATAFxxGyEACNcRKVNYRm8CToMKwAFmYQFqo2ABQAlM4ACurAGAA8ERYA2gC6AHzWBVoqAHQA5sExVJxl5mA6cSUwoeiMMTyokMzGVgUdXRgl9vQMcT6SfgG2uORQRNYoGNi4eDFIIisA0uh4zllUtZH1VDkANHAb+ABijM5BIeF1qoRjkpyccJ9fAHoA-OPAEhwGLFVAlVIAQSUKgAolBZjEZtA4nFEFJPkioOi4O84H8pIQgA)
3737

3838
[//]: # 'Playground6'
3939
[//]: # 'Materials'

packages/query-core/src/infiniteQueryObserver.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type {
66
InfiniteQueryObserverOptions,
77
InfiniteQueryObserverResult,
88
QueryKey,
9+
RegisteredError,
910
} from './types'
1011
import type { QueryClient } from './queryClient'
1112
import type { NotifyOptions, ObserverFetchOptions } from './queryObserver'
@@ -23,7 +24,7 @@ type InfiniteQueryObserverListener<TData, TError> = (
2324

2425
export class InfiniteQueryObserver<
2526
TQueryFnData = unknown,
26-
TError = Error,
27+
TError = RegisteredError,
2728
TData = TQueryFnData,
2829
TQueryData = TQueryFnData,
2930
TQueryKey extends QueryKey = QueryKey,

packages/query-core/src/mutation.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import type { MutationOptions, MutationStatus, MutationMeta } from './types'
1+
import type {
2+
MutationOptions,
3+
MutationStatus,
4+
MutationMeta,
5+
RegisteredError,
6+
} from './types'
27
import type { MutationCache } from './mutationCache'
38
import type { MutationObserver } from './mutationObserver'
49
import { notifyManager } from './notifyManager'
@@ -19,7 +24,7 @@ interface MutationConfig<TData, TError, TVariables, TContext> {
1924

2025
export interface MutationState<
2126
TData = unknown,
22-
TError = Error,
27+
TError = RegisteredError,
2328
TVariables = void,
2429
TContext = unknown,
2530
> {
@@ -75,7 +80,7 @@ export type Action<TData, TError, TVariables, TContext> =
7580

7681
export class Mutation<
7782
TData = unknown,
78-
TError = Error,
83+
TError = RegisteredError,
7984
TVariables = void,
8085
TContext = unknown,
8186
> extends Removable {

packages/query-core/src/mutationCache.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { MutationObserver } from './mutationObserver'
2-
import type { MutationOptions } from './types'
2+
import type { MutationOptions, RegisteredError } from './types'
33
import type { QueryClient } from './queryClient'
44
import { notifyManager } from './notifyManager'
55
import type { Action, MutationState } from './mutation'
@@ -127,7 +127,12 @@ export class MutationCache extends Subscribable<MutationCacheListener> {
127127
return this.#mutations
128128
}
129129

130-
find<TData = unknown, TError = Error, TVariables = any, TContext = unknown>(
130+
find<
131+
TData = unknown,
132+
TError = RegisteredError,
133+
TVariables = any,
134+
TContext = unknown,
135+
>(
131136
filters: MutationFilters,
132137
): Mutation<TData, TError, TVariables, TContext> | undefined {
133138
if (typeof filters.exact === 'undefined') {

packages/query-core/src/mutationObserver.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type {
88
MutationObserverBaseResult,
99
MutationObserverResult,
1010
MutationObserverOptions,
11+
RegisteredError,
1112
} from './types'
1213
import { shallowEqualObjects } from './utils'
1314

@@ -26,7 +27,7 @@ interface NotifyOptions {
2627

2728
export class MutationObserver<
2829
TData = unknown,
29-
TError = Error,
30+
TError = RegisteredError,
3031
TVariables = void,
3132
TContext = unknown,
3233
> extends Subscribable<

packages/query-core/src/query.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type {
99
CancelOptions,
1010
SetDataOptions,
1111
FetchStatus,
12+
RegisteredError,
1213
} from './types'
1314
import type { QueryCache } from './queryCache'
1415
import type { QueryObserver } from './queryObserver'
@@ -33,7 +34,7 @@ interface QueryConfig<
3334
state?: QueryState<TData, TError>
3435
}
3536

36-
export interface QueryState<TData = unknown, TError = Error> {
37+
export interface QueryState<TData = unknown, TError = RegisteredError> {
3738
data: TData | undefined
3839
dataUpdateCount: number
3940
dataUpdatedAt: number
@@ -64,7 +65,7 @@ export interface FetchContext<
6465

6566
export interface QueryBehavior<
6667
TQueryFnData = unknown,
67-
TError = Error,
68+
TError = RegisteredError,
6869
TData = TQueryFnData,
6970
TQueryKey extends QueryKey = QueryKey,
7071
> {
@@ -137,7 +138,7 @@ export interface SetStateOptions {
137138

138139
export class Query<
139140
TQueryFnData = unknown,
140-
TError = Error,
141+
TError = RegisteredError,
141142
TData = TQueryFnData,
142143
TQueryKey extends QueryKey = QueryKey,
143144
> extends Removable {

packages/query-core/src/queryCache.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@ import type { QueryFilters } from './utils'
22
import { hashQueryKeyByOptions, matchQuery } from './utils'
33
import type { Action, QueryState } from './query'
44
import { Query } from './query'
5-
import type { QueryKey, QueryOptions, WithRequired } from './types'
5+
import type {
6+
QueryKey,
7+
QueryOptions,
8+
RegisteredError,
9+
WithRequired,
10+
} from './types'
611
import { notifyManager } from './notifyManager'
712
import type { QueryClient } from './queryClient'
813
import { Subscribable } from './subscribable'
@@ -134,7 +139,7 @@ export class QueryCache extends Subscribable<QueryCacheListener> {
134139

135140
get<
136141
TQueryFnData = unknown,
137-
TError = Error,
142+
TError = RegisteredError,
138143
TData = TQueryFnData,
139144
TQueryKey extends QueryKey = QueryKey,
140145
>(
@@ -149,7 +154,7 @@ export class QueryCache extends Subscribable<QueryCacheListener> {
149154
return [...this.#queries.values()]
150155
}
151156

152-
find<TQueryFnData = unknown, TError = Error, TData = TQueryFnData>(
157+
find<TQueryFnData = unknown, TError = RegisteredError, TData = TQueryFnData>(
153158
filters: WithRequired<QueryFilters, 'queryKey'>,
154159
): Query<TQueryFnData, TError, TData> | undefined {
155160
if (typeof filters.exact === 'undefined') {

packages/query-core/src/queryClient.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import type {
2525
ResetOptions,
2626
ResetQueryFilters,
2727
SetDataOptions,
28+
RegisteredError,
2829
} from './types'
2930
import type { QueryState } from './query'
3031
import { QueryCache } from './queryCache'
@@ -177,7 +178,7 @@ export class QueryClient {
177178
)
178179
}
179180

180-
getQueryState<TQueryFnData = unknown, TError = Error>(
181+
getQueryState<TQueryFnData = unknown, TError = RegisteredError>(
181182
queryKey: QueryKey,
182183
): QueryState<TQueryFnData, TError> | undefined {
183184
return this.#queryCache.find<TQueryFnData, TError>({ queryKey })?.state
@@ -276,7 +277,7 @@ export class QueryClient {
276277

277278
fetchQuery<
278279
TQueryFnData,
279-
TError = Error,
280+
TError = RegisteredError,
280281
TData = TQueryFnData,
281282
TQueryKey extends QueryKey = QueryKey,
282283
>(
@@ -298,7 +299,7 @@ export class QueryClient {
298299

299300
prefetchQuery<
300301
TQueryFnData = unknown,
301-
TError = Error,
302+
TError = RegisteredError,
302303
TData = TQueryFnData,
303304
TQueryKey extends QueryKey = QueryKey,
304305
>(
@@ -309,7 +310,7 @@ export class QueryClient {
309310

310311
fetchInfiniteQuery<
311312
TQueryFnData,
312-
TError = Error,
313+
TError = RegisteredError,
313314
TData = TQueryFnData,
314315
TQueryKey extends QueryKey = QueryKey,
315316
>(
@@ -321,7 +322,7 @@ export class QueryClient {
321322

322323
prefetchInfiniteQuery<
323324
TQueryFnData,
324-
TError = Error,
325+
TError = RegisteredError,
325326
TData = TQueryFnData,
326327
TQueryKey extends QueryKey = QueryKey,
327328
>(
@@ -405,7 +406,7 @@ export class QueryClient {
405406

406407
defaultQueryOptions<
407408
TQueryFnData = unknown,
408-
TError = Error,
409+
TError = RegisteredError,
409410
TData = TQueryFnData,
410411
TQueryData = TQueryFnData,
411412
TQueryKey extends QueryKey = QueryKey,

0 commit comments

Comments
 (0)