Attributes : ✅ Recommended • 🔧 Fixable
Overview
Query keys should be seen like a dependency array to your query function: Every variable that is used inside the queryFn should be added to the query key. This makes sure that queries are cached independently and that queries are refetched automatically when the variables change.
Rule Details
This rule enforces that all variables referenced in your query function are included in the query key. This is crucial for proper caching and automatic refetching behavior.
Why This Matters
Independent Caching : Different values should create different cache entries
Automatic Refetching : Queries refetch when dependencies change
Stale Data Prevention : Ensures you’re always working with the correct data
Examples
Incorrect Code
Simple Case
Query Factory
Multiple Dependencies
/* eslint "@tanstack/query/exhaustive-deps": "error" */
useQuery ({
queryKey: [ 'todo' ],
queryFn : () => api . getTodo ( todoId ), // ❌ todoId not in key
})
Correct Code
Simple Case
Query Factory
Multiple Dependencies
Object in Query Key
useQuery ({
queryKey: [ 'todo' , todoId ], // ✅ todoId included
queryFn : () => api . getTodo ( todoId ),
})
Common Patterns
User-Specific Queries
function useUserTodos ( userId : string ) {
return useQuery ({
queryKey: [ 'todos' , userId ],
queryFn : () => fetchUserTodos ( userId ),
})
}
Filtered Lists
function useTodos ( filters : TodoFilters ) {
return useQuery ({
queryKey: [ 'todos' , filters ],
queryFn : () => fetchTodos ( filters ),
})
}
Paginated Data
function useTodos ( page : number , pageSize : number ) {
return useQuery ({
queryKey: [ 'todos' , page , pageSize ],
queryFn : () => fetchTodos ( page , pageSize ),
})
}
Detail Views
function useTodo ( id : string , includeComments : boolean ) {
return useQuery ({
queryKey: [ 'todo' , id , { includeComments }],
queryFn : () => fetchTodo ( id , includeComments ),
})
}
What About Constants?
Constants that never change don’t need to be in the query key:
const API_URL = 'https://api.example.com' // constant
useQuery ({
queryKey: [ 'todos' ], // ✅ OK: API_URL doesn't need to be in key
queryFn : () => fetch ( ` ${ API_URL } /todos` ),
})
However, configuration that might change should be included:
const { apiUrl } = useConfig () // can change
useQuery ({
queryKey: [ 'todos' , apiUrl ], // ✅ Include changing config
queryFn : () => fetch ( ` ${ apiUrl } /todos` ),
})
Auto-Fix
This rule is auto-fixable. ESLint can automatically add missing dependencies to your query key:
Review auto-fixes carefully. The rule may not always detect the optimal query key structure.
When to Disable
You might want to disable this rule if:
You’re using a custom query key factory that handles dependencies differently
You’re intentionally sharing cache across different parameters (advanced use case)
You’re using a global query function that doesn’t depend on variables
// eslint-disable-next-line @tanstack/query/exhaustive-deps
useQuery ({
queryKey: [ 'constant-data' ],
queryFn : () => fetchConstantData ( dynamicParam ),
})
Disabling this rule can lead to stale data and caching bugs. Only do so if you fully understand the implications.
Best Practices
Create reusable query configurations with proper key structures: const todoKeys = {
all: [ 'todos' ] as const ,
lists : () => [ ... todoKeys . all , 'list' ] as const ,
list : ( filters : TodoFilters ) => [ ... todoKeys . lists (), filters ] as const ,
details : () => [ ... todoKeys . all , 'detail' ] as const ,
detail : ( id : string ) => [ ... todoKeys . details (), id ] as const ,
}
// Usage
useQuery ({
queryKey: todoKeys . detail ( id ),
queryFn : () => fetchTodo ( id ),
})
Ensure all query key values are serializable: // ✅ Good: primitive values and plain objects
queryKey : [ 'todos' , { status: 'active' , page: 1 }]
// ❌ Bad: functions, class instances
queryKey : [ 'todos' , () => {}, new Date ()]
Keep related queries together by using consistent key ordering: // ✅ Good: consistent hierarchy
[ 'todos' ] // all todos
[ 'todos' , { status: 'active' }] // filtered todos
[ 'todos' , 'detail' , id ] // specific todo
// ❌ Bad: inconsistent structure
[ 'todos' ]
[{ status: 'active' }, 'todos' ]
[ id , 'todos' , 'detail' ]
TypeScript Support
The rule works seamlessly with TypeScript and understands type information:
interface TodoFilters {
status : 'active' | 'completed'
priority ?: 'high' | 'low'
}
function useTodos ( filters : TodoFilters ) {
return useQuery ({
queryKey: [ 'todos' , filters ], // ✅ Properly typed
queryFn : () => fetchTodos ( filters ),
})
}
Further Reading
Query Keys Guide Learn best practices for structuring query keys
Effective Query Keys TkDodo’s guide to query key patterns