Typed Queries in React Query: From Basics to Generics
React Query is powerful for data fetching, but proper TypeScript integration can make it even better. In this guide, you’ll learn how to type useQuery, QueryOptions, and query functions with complete confidence.
TypeScript support helps you write resilient data hooks that scale — with autocomplete, compile-time checks, and reusability. We’ll walk through everything from basic usage to generic query wrappers.
✅ Basic Typing Example
Let’s start with a typical query:
import { useQuery, UseQueryOptions } from '@tanstack/react-query';
type User = {
id: string;
name: string;
};
const fetchUser = async (): Promise<User> => {
const res = await fetch('/api/user');
if (!res.ok) throw new Error('Failed to fetch');
return res.json();
};
export function useUserQuery() {
return useQuery<User>(['user'], fetchUser);
}🧠 Typing QueryOptions Manually
You can extract and reuse the UseQueryOptions type:
import { UseQueryOptions } from '@tanstack/react-query';
type User = { id: string; name: string };
const fetchUser = async (): Promise<User> => {
const res = await fetch('/api/user');
return res.json();
};
type UserQueryOptions = UseQueryOptions<
User, // TQueryFnData
Error, // TError
User, // TData
['user'] // TQueryKey
>;Usage:
export const useUserQuery = (options?: UserQueryOptions) => {
return useQuery(['user'], fetchUser, options);
};🧱 Typing Generic Query Hook
For reusable logic:
import { useQuery, UseQueryOptions, UseQueryResult } from '@tanstack/react-query';
export function useTypedQuery<TData, TError = Error, TQueryKey extends readonly unknown[] = any>(
key: TQueryKey,
queryFn: () => Promise<TData>,
options?: UseQueryOptions<TData, TError, TData, TQueryKey>
): UseQueryResult<TData, TError> {
return useQuery(key, queryFn, options);
}Usage:
type Product = { id: number; name: string };
function useProductQuery(id: number) {
return useTypedQuery<Product>(['product', id], () =>
fetch(`/api/products/${id}`).then(res => res.json())
);
}💡 Tip: queryFn infers return type
When queryFn is inline with a Promise<T> return:
useQuery(['user'], async (): Promise<User> => {
return fetch('/api/user').then(res => res.json());
});No extra typing needed!
📦 Bonus: With Axios
const fetchUsers = async (): Promise<User[]> => {
const { data } = await axios.get<User[]>('/api/users');
return data;
};
useQuery<User[], Error>(['users'], fetchUsers);✅ Summary
| Generic | Role |
|---|---|
TQueryFnData | Return value of queryFn |
TError | Error type |
TData | Data passed to your component |
TQueryKey | Key that identifies the query |
By mastering these generics, you can turn every query into a safe, typed, and reusable hook. This improves maintainability and prevents subtle bugs at scale.
Whether you’re building a hobby project or a production-grade dashboard, strong typing in React Query is an investment worth making.