Default query functions allow you to define a global query function that’s used when no queryFn is specified. This is useful for standardizing data fetching across your application.
Setting a Default Query Function
Configure a default query function when creating the QueryClient:
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
const queryClient = new QueryClient({
defaultOptions: {
queries: {
queryFn: async ({ queryKey }) => {
const response = await fetch(`https://api.example.com/${queryKey.join('/')}`)
if (!response.ok) {
throw new Error('Network response was not ok')
}
return response.json()
},
},
},
})
function App() {
return (
<QueryClientProvider client={queryClient}>
<YourApp />
</QueryClientProvider>
)
}
With a default query function, you can omit the queryFn option in individual queries.
Using Queries Without queryFn
Once configured, use queries without specifying queryFn:
import { useQuery } from '@tanstack/react-query'
function Users() {
// Uses default queryFn - fetches from: /api/users
const { data } = useQuery({
queryKey: ['api', 'users']
})
return (
<ul>
{data?.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)
}
function User({ id }: { id: number }) {
// Uses default queryFn - fetches from: /api/users/123
const { data } = useQuery({
queryKey: ['api', 'users', id]
})
return <div>{data?.name}</div>
}
Structure your query keys to match your API endpoints when using a default query function.
Overriding the Default
You can still provide a custom queryFn for specific queries:
import { useQuery } from '@tanstack/react-query'
function SpecialData() {
const { data } = useQuery({
queryKey: ['special'],
// Override the default queryFn for this query
queryFn: async () => {
const response = await fetch('https://different-api.com/data')
return response.json()
}
})
return <div>{data?.value}</div>
}
Advanced Default Function
Create a more sophisticated default function with error handling and authentication:
import { QueryClient } from '@tanstack/react-query'
const queryClient = new QueryClient({
defaultOptions: {
queries: {
queryFn: async ({ queryKey, signal, meta }) => {
const [baseUrl, ...pathParts] = queryKey as string[]
const path = pathParts.join('/')
const headers: HeadersInit = {
'Content-Type': 'application/json',
}
// Add auth token if available
const token = localStorage.getItem('authToken')
if (token) {
headers['Authorization'] = `Bearer ${token}`
}
// Support custom headers from meta
if (meta?.headers) {
Object.assign(headers, meta.headers)
}
const response = await fetch(`https://api.example.com/${path}`, {
headers,
signal, // Support query cancellation
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.message || 'An error occurred')
}
return response.json()
},
staleTime: 5 * 60 * 1000, // 5 minutes
retry: 3,
},
},
})
Using with TypeScript
Type your default query function for better type safety:
import { QueryClient, QueryKey } from '@tanstack/react-query'
interface ApiQueryKey extends Array<string | number> {
0: 'api'
}
const queryClient = new QueryClient({
defaultOptions: {
queries: {
queryFn: async ({ queryKey }: { queryKey: QueryKey }) => {
const [_, ...pathParts] = queryKey as ApiQueryKey
const path = pathParts.join('/')
const response = await fetch(`https://api.example.com/${path}`)
if (!response.ok) {
throw new Error('Network error')
}
return response.json()
},
},
},
})
Query-Specific Defaults
Set defaults for specific query keys using setQueryDefaults:
import { useQueryClient } from '@tanstack/react-query'
function App() {
const queryClient = useQueryClient()
// Set defaults for all 'users' queries
queryClient.setQueryDefaults(['users'], {
queryFn: async () => {
const res = await fetch('/api/users')
return res.json()
},
staleTime: 10 * 1000,
})
// Set defaults for all 'posts' queries
queryClient.setQueryDefaults(['posts'], {
queryFn: async () => {
const res = await fetch('/api/posts')
return res.json()
},
staleTime: 5 * 1000,
})
return <YourApp />
}
Source: packages/query-core/src/queryClient.ts:473
Define query defaults
Use setQueryDefaults with a query key prefix
Defaults apply to matching keys
Any query with a matching key prefix inherits these defaults
Use queries without options
Queries automatically use the configured defaults
Partial Matching
Query defaults use partial key matching:
queryClient.setQueryDefaults(['users'], {
queryFn: fetchUsers,
staleTime: 10000,
})
// ✅ Matches - inherits defaults
useQuery({ queryKey: ['users'] })
// ✅ Matches - inherits defaults (partial match)
useQuery({ queryKey: ['users', 'active'] })
// ✅ Matches - inherits defaults (partial match)
useQuery({ queryKey: ['users', { status: 'active' }] })
// ❌ Does not match - 'users' is not the prefix
useQuery({ queryKey: ['admin', 'users'] })
Query defaults match by prefix. The defaults key must be a prefix of the query key, not vice versa.
Source: packages/query-core/src/queryClient.ts:493
Getting Query Defaults
Retrieve defaults for a specific query key:
import { useQueryClient } from '@tanstack/react-query'
function Component() {
const queryClient = useQueryClient()
const defaults = queryClient.getQueryDefaults(['users'])
console.log(defaults)
// { queryFn: [Function], staleTime: 10000, ... }
}
Merging with Other Defaults
Query defaults merge with global defaults and query-specific options:
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 0,
retry: 3,
},
},
})
queryClient.setQueryDefaults(['users'], {
staleTime: 10000,
refetchOnWindowFocus: false,
})
// Final options for this query:
useQuery({
queryKey: ['users'],
gcTime: 60000,
})
// Results in:
// {
// staleTime: 10000, // from setQueryDefaults
// retry: 3, // from global defaults
// refetchOnWindowFocus: false, // from setQueryDefaults
// gcTime: 60000, // from query options
// }
Real-World Example
A complete setup for a REST API:
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
class ApiError extends Error {
constructor(public status: number, message: string) {
super(message)
}
}
const queryClient = new QueryClient({
defaultOptions: {
queries: {
queryFn: async ({ queryKey, signal }) => {
const [_api, ...pathParts] = queryKey as [string, ...(string | number)[]]
const url = `${import.meta.env.VITE_API_URL}/${pathParts.join('/')}`
const response = await fetch(url, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${getAuthToken()}`,
},
signal,
})
if (!response.ok) {
throw new ApiError(response.status, await response.text())
}
return response.json()
},
staleTime: 30 * 1000,
retry: (failureCount, error) => {
if (error instanceof ApiError && error.status === 404) {
return false // Don't retry 404s
}
return failureCount < 3
},
},
},
})
// Now use queries without queryFn:
function Users() {
const { data } = useQuery({ queryKey: ['api', 'users'] })
return <div>{data?.length} users</div>
}
function User({ id }: { id: number }) {
const { data } = useQuery({ queryKey: ['api', 'users', id] })
return <div>{data?.name}</div>
}
When to Use Default Query Functions
Use default query functions when:
- Your API follows consistent patterns
- You want to centralize auth, error handling, or logging
- You have many similar queries
- You want to reduce boilerplate
Avoid when:
- Your queries fetch from different sources
- Each query needs unique logic
- You prefer explicit queryFn declarations
See Also