Typed Queries in React Query: From Basics to Generics

August, 6th 2025 2 min read

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:

ts
12345678910111213141516
      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:

ts
123456789101112131415
      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:

ts
123
      export const useUserQuery = (options?: UserQueryOptions) => {
  return useQuery(['user'], fetchUser, options);
};
    

🧱 Typing Generic Query Hook

For reusable logic:

ts
123456789
      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:

ts
1234567
      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:

ts
123
      useQuery(['user'], async (): Promise<User> => {
  return fetch('/api/user').then(res => res.json());
});
    

No extra typing needed!


📦 Bonus: With Axios

ts
123456
      const fetchUsers = async (): Promise<User[]> => {
  const { data } = await axios.get<User[]>('/api/users');
  return data;
};

useQuery<User[], Error>(['users'], fetchUsers);
    

✅ Summary

GenericRole
TQueryFnDataReturn value of queryFn
TErrorError type
TDataData passed to your component
TQueryKeyKey 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.