React Query is now written in TypeScript to make sure the library and your projects are type-safe!
Things to keep in mind:
- Types currently require using TypeScript v4.7 or greater
- Changes to types in this repository are considered non-breaking and are usually released as patch semver changes (otherwise every type enhancement would be a major version!).
- It is highly recommended that you lock your react-query package version to a specific patch release and upgrade with the expectation that types may be fixed or upgraded between any release
- The non-type-related public API of React Query still follows semver very strictly.
Type Inference #
Types in React Query generally flow through very well so that you don't have to provide type annotations for yourself
1const { data } = useQuery({
2 // ^? const data: number | undefined
3 queryKey: ['test'],
4 queryFn: () => Promise.resolve(5),
5})
1const { data } = useQuery({
2 // ^? const data: string | undefined
3 queryKey: ['test'],
4 queryFn: () => Promise.resolve(5),
5 select: (data) => data.toString(),
6})
This works best if your queryFn
has a well-defined returned type. Keep in mind that most data fetching libraries return any
per default, so make sure to extract it to a properly typed function:
1const fetchGroups = (): Promise<Group[]> =>
2 axios.get('/groups').then((response) => response.data)
3
4const { data } = useQuery({ queryKey: ['groups'], queryFn: fetchGroups })
5// ^? const data: Group[] | undefined
Type Narrowing #
React Query uses a discriminated union type for the query result, discriminated by the status
field and the derived status boolean flags. This will allow you to check for e.g. success
status to make data
defined:
1const { data, isSuccess } = useQuery({
2 queryKey: ['test'],
3 queryFn: () => Promise.resolve(5),
4})
5
6if (isSuccess) {
7 data
8 // ^? const data: number
9}
Typing the error field #
The type for error defaults to Error
, because that is what most users expect.
1const { error } = useQuery({ queryKey: ['groups'], queryFn: fetchGroups })
2// ^? const error: Error
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:
1const { error } = useQuery<Group[], string>(['groups'], fetchGroups)
2// ^? const error: string | null
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 an Error
, so if you have a subclass like AxiosError
you can use type narrowing to make the error field more specific:
1import axios from 'axios'
2
3const { error } = useQuery({ queryKey: ['groups'], queryFn: fetchGroups })
4// ^? const error: Error | null
5
6if (axios.isAxiosError(error)) {
7 error
8 // ^? const error: AxiosError
9}
Registering a global Error #
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:
1import '@tanstack/react-query'
2
3declare module '@tanstack/react-query' {
4 interface Register {
5 defaultError: AxiosError
6 }
7}
8
9const { error } = useQuery({ queryKey: ['groups'], queryFn: fetchGroups })
10// ^? const error: AxiosError | null
Typing meta #
Registering global Meta #
Similarly to registering a global error type you can also register a global Meta
type. This ensures the optional meta
field on queries and mutations stays consistent and is type-safe. Note that the registered type must extend Record<string, unknown>
so that meta
remains an object.
1import '@tanstack/react-query'
2
3interface MyMeta extends Record<string, unknown> {
4 // Your meta type definition.
5}
6
7declare module '@tanstack/react-query' {
8 interface Register {
9 queryMeta: MyMeta
10 mutationMeta: MyMeta
11 }
12}
Typing Query Options #
If you inline query options into useQuery
, you'll get automatic type inference. However, you might want to extract the query options into a separate function to share them between useQuery
and e.g. prefetchQuery
. In that case, you'd lose type inference. To get it back, you can use queryOptions
helper:
1import { queryOptions } from '@tanstack/react-query'
2
3function groupOptions() {
4 return queryOptions({
5 queryKey: ['groups'],
6 queryFn: fetchGroups,
7 staleTime: 5 * 1000,
8 })
9}
10
11useQuery(groupOptions())
12queryClient.prefetchQuery(groupOptions())
Further, the queryKey
returned from queryOptions
knows about the queryFn
associated with it, and we can leverage that type information to make functions like queryClient.getQueryData
aware of those types as well:
1function groupOptions() {
2 return queryOptions({
3 queryKey: ['groups'],
4 queryFn: fetchGroups,
5 staleTime: 5 * 1000,
6 })
7}
8
9const data = queryClient.getQueryData(groupOptions().queryKey)
10// ^? const data: Group[] | undefined
Without queryOptions
, the type of data
would be unknown
, unless we'd pass a generic to it:
1const data = queryClient.getQueryData<Group[]>(['groups'])
Further Reading #
For tips and tricks around type inference, have a look at React Query and TypeScript from the Community Resources. To find out how to get the best possible type-safety, you can read Type-safe React Query.
Typesafe disabling of queries using skipToken
#
If you are using TypeScript, you can use the skipToken
to disable a query. This is useful when you want to disable a query based on a condition, but you still want to keep the query to be type safe.
Read more about it in the Disabling Queries guide.