Capítulo 46 de 80

Chapter 46: ESLint Plugin Query

Core Idea

@tanstack/eslint-plugin-query catches the mistakes this skill's guides repeatedly warn about — missing key dependencies, unstable QueryClient instances, rest-destructuring that defeats tracked properties — as lint errors instead of runtime bugs; install it via flat/recommended (or flat/recommended-strict for more opinionated rules) and it enforces most of these patterns automatically.

Key Concepts

  • Install & config: separate package (npm i -D @tanstack/eslint-plugin-query); flat config spreads pluginQuery.configs['flat/recommended'] (or 'flat/recommended-strict') into the config array, or you can cherry-pick individual rules under the @tanstack/query plugin namespace. Legacy .eslintrc uses "extends": ["plugin:@tanstack/query/recommended"] the same way.
  • exhaustive-deps (recommended, fixable): enforces that every value a queryFn closes over is present in queryKey — the automated version of the Query Keys chapter's dependency rule. Function call targets (fetchTodoById itself) aren't dependencies; values referenced inside nested callbacks still are. Configurable allowlist.variables/allowlist.types to exempt stable values (e.g. an injected API client) from the check.
  • stable-query-client (recommended, fixable): flags constructing new QueryClient() inline in a component body (a new client every render) — require it in useState(() => new QueryClient()), module scope, or (the one exception) an async Server Component, which only runs once server-side.
  • no-rest-destructuring (recommended, not fixable): flags const { data, ...rest } = useQuery(...) because rest destructuring subscribes to every field, silently defeating tracked-properties re-render skipping (Chapter 39). Also flags rest destructuring on custom hooks that themselves return a query result, under typed linting. Safe to disable if notifyOnChangeProps is already being set manually.
  • no-unstable-deps (recommended, not fixable): flags passing a whole query/mutation hook's return object directly into another hook's dependency array (useEffect/useMemo/useCallback) — that object isn't referentially stable across renders. Destructure the specific value(s) needed (e.g. mutate from useMutation) and depend on those instead.
  • infinite-query-property-order (recommended, fixable): for useInfiniteQuery/useSuspenseInfiniteQuery/infiniteQueryOptions, TypeScript's inference is order-sensitive — queryFn, then getPreviousPageParam, then getNextPageParam must appear in that relative order for correct type inference; other properties are order-insensitive.
  • no-void-query-fn (recommended, not fixable): flags a queryFn that never returns a value (e.g. await api.todos.fetch() with no return) — this silently produces undefined, an illegal successful cache value.
  • mutation-property-order (recommended, fixable): same order-sensitivity issue as infinite queries, but for useMutation()onMutate, then onError, then onSettled must appear in that relative order.
  • prefer-query-options (recommended in the strict config only, not fixable): flags a bare { queryKey, queryFn } object passed straight to useQuery instead of wrapped in queryOptions() — and separately flags re-typing a queryKey array literal in queryClient calls (getQueryData(['todo', id])) instead of reusing optionsFn(id).queryKey from the shared queryOptions() definition, which risks the literal drifting out of sync with the real key.

Reference Tables

RuleRecommendedAuto-fixableEnforces
exhaustive-depsYesYesEvery queryFn dependency is in queryKey
stable-query-clientYesYesQueryClient isn't recreated every render
no-rest-destructuringYesNoNo rest-destructuring of query results
no-unstable-depsYesNoQuery hook results aren't passed whole into dep arrays
infinite-query-property-orderYesYesqueryFngetPreviousPageParamgetNextPageParam order
no-void-query-fnYesNoqueryFn always returns a value
mutation-property-orderYesYesonMutateonErroronSettled order
prefer-query-optionsStrict onlyNoqueryOptions() used instead of raw key/fn pairs, key reused not re-typed

Key Takeaways

  1. Start with flat/recommended on any new project — it's the automated enforcement of nearly every "gotcha" covered across the Guides chapters, and half the rules auto-fix.
  2. Reach for the allowlist option on exhaustive-deps for genuinely stable injected values (API clients, config objects) rather than disabling the rule wholesale.
  3. Adopt flat/recommended-strict (which adds prefer-query-options) once a codebase has more than a couple of shared query keys — it prevents key literals from silently drifting apart across call sites.

Connects To

  • Query Keys: the dependency rule exhaustive-deps automates.
  • Render Optimizations: tracked properties, protected by no-rest-destructuring.
  • Query Options: queryOptions(), enforced by prefer-query-options.