Skip to Content

TanStack Query

TanStack Query rules from @tanstack/eslint-plugin-query.

They guard the ergonomic and correctness contracts of TanStack Query (useQuery, useMutation, query-options factories) inside React TypeScript sources.

Source: @tanstack/eslint-plugin-query (MIT).

Rule index

Each rule name links to the detailed section below.

Examples come from the checked lint corpus or package-level rule tests when project layout matters.

Rules

tanstack-query/exhaustive-deps

Require TanStack Query queryKey arrays to include every variable read by the queryFn body, mirroring React Hooks dependency tracking.

Example:

export function useTodo(todoId: string) { return useQuery({ queryKey: ["todo"], // reports: tanstack-query/exhaustive-deps (error) queryFn: () => fetchTodo(todoId), }); }

tanstack-query/infinite-query-property-order

Require queryFn, getPreviousPageParam, and getNextPageParam inside useInfiniteQuery to appear in the order TanStack Query documents.

Type inference flows through these callbacks in sequence, so a reordered options object widens the page-param type to unknown.

Example:

export function usePages() { return useInfiniteQuery({ queryKey: ["pages"], // reports: tanstack-query/infinite-query-property-order (error) getNextPageParam: (last) => last.next, queryFn: ({ pageParam }) => pageParam, }); }

tanstack-query/mutation-property-order

Require useMutation callbacks to declare onMutate before onError and onSettled.

The options object’s type inference threads the onMutate return value into the later callbacks’ context parameter, which collapses to unknown when the order is wrong.

Example:

export function useSave() { return useMutation({ mutationFn: async (input: string) => input, // reports: tanstack-query/mutation-property-order (error) onError: () => {}, onMutate: () => ({ snapshot: true }), }); }

tanstack-query/no-rest-destructuring

Reject ...rest destructuring on TanStack Query hook results.

The result object is a tracked proxy that only re-renders for the fields you read; rest-destructuring touches every field, subscribes the component to all of them, and disables that optimization.

Example:

import { useQuery } from "@tanstack/react-query"; export function Todos() { // reports: tanstack-query/no-rest-destructuring (error) const { data, ...rest } = useQuery({ queryKey: ["todos"], queryFn: () => ["todo"], }); return data ?? rest.status; }

tanstack-query/no-unstable-deps

Reject passing entire TanStack Query hook results into React dependency arrays.

The returned object is a fresh reference on every render, so useEffect / useMemo / useCallback would re-run unconditionally; depend on the specific fields you read instead.

Example:

export function Todos() { const result = useQuery({ queryKey: ["todos"], queryFn: () => ["todo"], }); // reports: tanstack-query/no-unstable-deps (error) React.useEffect(() => {}, [result]); return result.data; }

tanstack-query/no-void-query-fn

Reject queryFn callbacks that resolve to void.

The return value is what TanStack Query caches and exposes as data; a void implementation always populates the cache with undefined and almost always indicates a forgotten return.

Example:

export function useTodos() { return useQuery({ queryKey: ["todos"], // reports: tanstack-query/no-void-query-fn (error) queryFn: () => { console.log("missing return"); }, }); }

tanstack-query/prefer-query-options

Prefer wrapping query options in the queryOptions() helper over inline { queryKey, queryFn } literals.

The helper co-locates key and fetcher, lets queryClient.getQueryData and setQueryData share the same typed key, and prevents the same key being paired with two different queryFns.

Example:

import { useQuery } from "@tanstack/react-query"; import { fetchTodo } from "./api"; export function useTodo(todoId: string) { // reports: tanstack-query/prefer-query-options (error) return useQuery({ queryKey: ["todo", todoId], queryFn: () => fetchTodo(todoId), }); }

tanstack-query/stable-query-client

Reject creating a QueryClient inside a React component or hook body, the client must be stable across renders.

Example:

import { QueryClient } from "@tanstack/react-query"; export function TodosProvider() { // reports: tanstack-query/stable-query-client (error) const client = new QueryClient(); return client; }
Last updated on