snapshot of https://github.com/TanStack/query.git (main, 2026-09-16; blobs > 131072B omitted)
This commit is contained in:
21
examples/react/eslint-plugin-demo/eslint.config.js
Normal file
21
examples/react/eslint-plugin-demo/eslint.config.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import pluginQuery from '@tanstack/eslint-plugin-query'
|
||||
import tseslint from 'typescript-eslint'
|
||||
|
||||
export default [
|
||||
...tseslint.configs.recommended,
|
||||
...pluginQuery.configs['flat/recommended-strict'],
|
||||
{
|
||||
files: ['src/**/*.ts', 'src/**/*.tsx'],
|
||||
rules: {
|
||||
'@tanstack/query/exhaustive-deps': [
|
||||
'error',
|
||||
{
|
||||
allowlist: {
|
||||
variables: ['api'],
|
||||
types: ['AnalyticsClient'],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
27
examples/react/eslint-plugin-demo/package.json
Normal file
27
examples/react/eslint-plugin-demo/package.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "@tanstack/query-example-eslint-plugin-demo",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test:eslint": "eslint ./src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tanstack/react-query": "^5.102.8",
|
||||
"react": "^19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tanstack/eslint-plugin-query": "^5.102.8",
|
||||
"eslint": "^9.39.0",
|
||||
"typescript": "5.8.3",
|
||||
"typescript-eslint": "^8.48.0"
|
||||
},
|
||||
"nx": {
|
||||
"targets": {
|
||||
"test:eslint": {
|
||||
"dependsOn": [
|
||||
"^build"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
55
examples/react/eslint-plugin-demo/src/allowlist-demo.tsx
Normal file
55
examples/react/eslint-plugin-demo/src/allowlist-demo.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import { queryOptions } from '@tanstack/react-query'
|
||||
|
||||
export function todosOptions(userId: string) {
|
||||
const api = useApiClient()
|
||||
return queryOptions({
|
||||
// ✅ passes: 'api' is in allowlist.variables
|
||||
queryKey: ['todos', userId],
|
||||
queryFn: () => api.fetchTodos(userId),
|
||||
})
|
||||
}
|
||||
|
||||
export function todosByApiOptions(userId: string) {
|
||||
const todoApi = useApiClient()
|
||||
// ❌ fails: 'api' is in allowlist.variables, but this variable is named 'todoApi'
|
||||
// eslint-disable-next-line @tanstack/query/exhaustive-deps -- The following dependencies are missing in your queryKey: todoApi
|
||||
return queryOptions({
|
||||
queryKey: ['todos', userId],
|
||||
queryFn: () => todoApi.fetchTodos(userId),
|
||||
})
|
||||
}
|
||||
|
||||
export function todosWithTrackingOptions(
|
||||
tracker: AnalyticsClient,
|
||||
userId: string,
|
||||
) {
|
||||
return queryOptions({
|
||||
// ✅ passes: AnalyticsClient is in allowlist.types
|
||||
queryKey: ['todos', userId],
|
||||
queryFn: async () => {
|
||||
tracker.track('todos')
|
||||
return fetch(`/api/todos?userId=${userId}`).then((r) => r.json())
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function todosWithClientOptions(client: ApiClient, userId: string) {
|
||||
// ❌ fails: AnalyticsClient is in allowlist.types, but this param is typed as ApiClient
|
||||
// eslint-disable-next-line @tanstack/query/exhaustive-deps -- The following dependencies are missing in your queryKey: client
|
||||
return queryOptions({
|
||||
queryKey: ['todos', userId],
|
||||
queryFn: () => client.fetchTodos(userId),
|
||||
})
|
||||
}
|
||||
|
||||
interface ApiClient {
|
||||
fetchTodos: (userId: string) => Promise<Array<{ id: string }>>
|
||||
}
|
||||
|
||||
interface AnalyticsClient {
|
||||
track: (event: string) => Promise<void>
|
||||
}
|
||||
|
||||
function useApiClient(): ApiClient {
|
||||
throw new Error('not implemented')
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { todoOptions, todosOptions } from './queries'
|
||||
|
||||
// ✅ passes: consuming imported queryOptions result directly
|
||||
export function Todos() {
|
||||
const query = useQuery(todosOptions)
|
||||
return query.data
|
||||
}
|
||||
|
||||
// ✅ passes: spreading imported queryOptions result with additional options
|
||||
export function Todo({ id }: { id: string }) {
|
||||
const query = useQuery({
|
||||
...todoOptions(id),
|
||||
select: (data) => data.title,
|
||||
})
|
||||
return query.data
|
||||
}
|
||||
|
||||
// ✅ passes: referencing queryKey from imported queryOptions result
|
||||
export function invalidateTodos() {
|
||||
const queryClient = useQueryClient()
|
||||
queryClient.invalidateQueries({ queryKey: todosOptions.queryKey })
|
||||
}
|
||||
|
||||
// ❌ fails: inline queryKey + queryFn should use queryOptions()
|
||||
export function TodosBad() {
|
||||
const query = useQuery(
|
||||
// eslint-disable-next-line @tanstack/query/prefer-query-options -- Prefer using queryOptions() or infiniteQueryOptions() to co-locate queryKey and queryFn.
|
||||
{
|
||||
queryKey: ['todos'],
|
||||
queryFn: () => fetch('/api/todos').then((r) => r.json()),
|
||||
},
|
||||
)
|
||||
return query.data
|
||||
}
|
||||
|
||||
// ❌ fails: inline queryKey should reference queryOptions result
|
||||
export function InvalidateTodosBad() {
|
||||
const queryClient = useQueryClient()
|
||||
// eslint-disable-next-line @tanstack/query/prefer-query-options -- Prefer referencing a queryKey from a queryOptions() result instead of typing it manually.
|
||||
queryClient.invalidateQueries({ queryKey: ['todos'] })
|
||||
}
|
||||
|
||||
// ❌ fails: inline queryKey as direct parameter
|
||||
export function GetTodosDataBad() {
|
||||
const queryClient = useQueryClient()
|
||||
// eslint-disable-next-line @tanstack/query/prefer-query-options -- Prefer referencing a queryKey from a queryOptions() result instead of typing it manually.
|
||||
return queryClient.getQueryData(['todos'])
|
||||
}
|
||||
23
examples/react/eslint-plugin-demo/src/queries.tsx
Normal file
23
examples/react/eslint-plugin-demo/src/queries.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { queryOptions } from '@tanstack/react-query'
|
||||
|
||||
// ✅ passes: queryKey and queryFn are co-located via queryOptions()
|
||||
export const todosOptions = queryOptions({
|
||||
queryKey: ['todos'],
|
||||
queryFn: () => fetchTodos(),
|
||||
})
|
||||
|
||||
// ✅ passes: factory function wrapping queryOptions()
|
||||
export const todoOptions = (id: string) =>
|
||||
queryOptions({
|
||||
queryKey: ['todo', id],
|
||||
queryFn: () => fetchTodo(id),
|
||||
})
|
||||
|
||||
function fetchTodos(): Promise<Array<{ id: string; title: string }>> {
|
||||
throw new Error('not implemented')
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
function fetchTodo(id: string): Promise<{ id: string; title: string }> {
|
||||
throw new Error('not implemented')
|
||||
}
|
||||
14
examples/react/eslint-plugin-demo/tsconfig.json
Normal file
14
examples/react/eslint-plugin-demo/tsconfig.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"lib": ["ES2020", "DOM"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "Bundler",
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"strict": true
|
||||
},
|
||||
"include": ["src", "eslint.config.js"]
|
||||
}
|
||||
Reference in New Issue
Block a user