new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

TanStack Query · Reference · all subjects

eslint-plugin-query

55 notes, read out of this brain and free to use. Each one was extracted from a source and is re-checked against its exam.

Legacy config custom setup

To configure only specific ESLint rules using legacy config (.eslintrc), add '@tanstack/query' to the plugins array and specify individual rules in the rules object. For example: '@tanstack/query/exhaustive-deps': 'error'.

Available ESLint rules

The TanStack Query ESLint plugin provides the following rules: @tanstack/query/exhaustive-deps, @tanstack/query/no-rest-destructuring, @tanstack/query/stable-query-client, @tanstack/query/no-unstable-deps, @tanstack/query/infinite-query-property-order, @tanstack/query/no-void-query-fn, @tanstack/query/mutation-property-order, and @tanstack/query/prefer-query-options.

ESLint plugin installation

The TanStack Query ESLint plugin is installed as a separate package using npm install -D @tanstack/eslint-plugin-query, pnpm add -D @tanstack/eslint-plugin-query, yarn add -D @tanstack/eslint-plugin-query, or bun add -D @tanstack/eslint-plugin-query.

Flat config recommended setup

To enable all recommended ESLint rules for the plugin using flat config (eslint.config.js), import pluginQuery from '@tanstack/eslint-plugin-query' and spread pluginQuery.configs['flat/recommended'] in the export default array.

Flat config recommended strict setup

To enable a stricter set of rules using flat config (eslint.config.js), spread pluginQuery.configs['flat/recommended-strict'] in the export default array. The flat/recommended-strict config extends flat/recommended with additional opinionated rules that enforce best practices more aggressively.

Flat config custom setup

To configure only specific ESLint rules using flat config, add a plugins object with '@tanstack/query': pluginQuery, then specify individual rules. For example: '@tanstack/query/exhaustive-deps': 'error'.

Legacy config recommended setup

To enable all recommended ESLint rules for the plugin using legacy config (.eslintrc), add 'plugin:@tanstack/query/recommended' to the extends array.

Legacy config recommended strict setup

To enable a stricter set of rules using legacy config (.eslintrc), add 'plugin:@tanstack/query/recommendedStrict' to the extends array. The recommendedStrict config extends recommended with additional opinionated rules.

exhaustive-deps correct usage

Query keys must include all values that affect the query result. Use queryKey: ['todo', todoId] when todoId is used in queryFn. This applies whether using useQuery directly or in factory functions like detail: (id) => ({ queryKey: ['todo', id], queryFn: () => todos.getTodo(id) }).

exhaustive-deps attributes

The exhaustive-deps rule is recommended and fixable.

exhaustive-deps rule purpose

The exhaustive-deps ESLint rule ensures that query keys contain all serializable values that identify the data returned by queryFn. This makes sure queries are cached independently and refetched automatically when those values change.

exhaustive-deps function calls not dependencies

Function call targets are not query key dependencies. For example, fetchTodoById(todoId) needs todoId in the query key, but not fetchTodoById itself. Values referenced inside nested callbacks are still dependencies, so promise.then(() => todoId) needs todoId in the query key.

exhaustive-deps incorrect example

Using useQuery with queryKey: ['todo'] and queryFn: () => api.getTodo(todoId) is incorrect because todoId is referenced in queryFn but not in queryKey. Similarly, a todo queries object with detail: (id) => ({ queryKey: ['todo'], queryFn: () => api.getTodo(id) }) is incorrect because id should be in queryKey.

exhaustive-deps allowlist option

The exhaustive-deps rule accepts an options object with an allowlist property containing two sub-properties: allowlist.variables (array of variable names to ignore) and allowlist.types (array of TypeScript type names to ignore). Example configuration: { "@tanstack/query/exhaustive-deps": ["error", { "allowlist": { "variables": ["api", "config"], "types": ["ApiClient", "Config"] } }] }

infinite-query-property-order affected functions

The infinite-query-property-order ESLint rule applies to these functions: useInfiniteQuery, useSuspenseInfiniteQuery, and infiniteQueryOptions.

infinite-query-property-order correct example

This example shows correct property ordering for useInfiniteQuery with queryFn first, followed by getPreviousPageParam, then getNextPageParam: ```tsx const query = useInfiniteQuery({ queryKey: ['projects'], queryFn: async ({ pageParam }) => { const response = await fetch(`/api/projects?cursor=${pageParam}`) return await response.json() }, initialPageParam: 0, getPreviousPageParam: (firstPage) => firstPage.previousId ?? undefined, getNextPageParam: (lastPage) => lastPage.nextId ?? undefined, maxPages: 3, }) ```

infinite-query-property-order ESLint rule

The @tanstack/query/infinite-query-property-order ESLint rule enforces correct property ordering for functions where property order matters due to type inference. This rule applies to useInfiniteQuery, useSuspenseInfiniteQuery, and infiniteQueryOptions. The required property order is: queryFn, getPreviousPageParam, getNextPageParam. All other properties are insensitive to order and can appear anywhere. The rule is recommended and fixable.

Correct property order for infinite queries

For useInfiniteQuery, useSuspenseInfiniteQuery, and infiniteQueryOptions, properties must be ordered as follows for correct type inference: queryFn must come first, followed by getPreviousPageParam, then getNextPageParam. Other properties like queryKey, initialPageParam, and maxPages can appear in any order.

useMutation property order for type inference

In useMutation(), the properties onMutate, onError, and onSettled must appear in that specific order due to type inference dependencies. The correct order is: onMutate first, then onError, then onSettled. All other properties like mutationFn can appear in any position.

mutation-property-order correct example

The following is correct code with properties in the right order: const mutation = useMutation({ mutationFn: () => Promise.resolve('success'), onMutate: async () => { results.push('onMutate-async'); await sleep(1); return { backup: 'async-data' }; }, onError: async () => { results.push('onError-async-start'); await sleep(1); results.push('onError-async-end'); }, onSettled: () => { results.push('onSettled-promise'); return Promise.resolve('also-ignored'); }, });

mutation-property-order incorrect example

The following is incorrect code because onSettled appears before onMutate and onError: const mutation = useMutation({ mutationFn: () => Promise.resolve('success'), onSettled: () => { results.push('onSettled-promise'); return Promise.resolve('also-ignored'); }, onMutate: async () => { results.push('onMutate-async'); await sleep(1); return { backup: 'async-data' }; }, onError: async () => { results.push('onError-async-start'); await sleep(1); results.push('onError-async-end'); }, });

mutation-property-order rule enforces callback order in useMutation

The eslint-plugin-query rule 'mutation-property-order' ensures that properties in useMutation() are ordered correctly for type inference. The required order is: onMutate, onError, onSettled. Other properties are insensitive to order. The rule is recommended and fixable.

no-rest-destructuring incorrect example

The following code is flagged by the no-rest-destructuring rule: const { data: todos, ...rest } = useQuery({ queryKey: ['todos'], queryFn: () => api.getTodos() }). This pattern uses rest destructuring which causes unnecessary subscriptions to all fields.

no-rest-destructuring correct example

The following code is correct for the no-rest-destructuring rule: const todosQuery = useQuery({ queryKey: ['todos'], queryFn: () => api.getTodos() }); const { data: todos } = todosQuery. Normal object destructuring without rest syntax is fine and only subscribes to the needed fields.

no-rest-destructuring can be disabled with notifyOnChangeProps

If you set the notifyOnChangeProps option manually, you can disable the no-rest-destructuring rule. When using notifyOnChangeProps, you are not using tracked queries and are responsible for specifying which props should trigger a re-render.

no-rest-destructuring rule attributes

The no-rest-destructuring rule is marked as recommended (✅). It is not fixable (no 🔧 indicator).

no-rest-destructuring rule catches object rest destructuring on query results

The @tanstack/query/no-rest-destructuring ESLint rule flags code that uses object rest destructuring (...rest) on query results. This is because rest destructuring automatically subscribes to every field of the query result, which may cause unnecessary re-renders. The rule ensures that you only subscribe to the fields that you actually need.

no-rest-destructuring rule with typed linting

When typed linting is enabled, the no-rest-destructuring rule also flags rest destructuring on custom hooks that return a TanStack Query result.

no-unstable-deps ESLint rule ID

The ESLint rule ID is '@tanstack/query/no-unstable-deps'.

no-unstable-deps purpose

The no-unstable-deps rule disallows putting the result of query hooks directly in a React hook dependency array. The objects returned from TanStack Query hooks are not referentially stable and should not be passed directly to React hook dependency arrays like those in useEffect, useMemo, or useCallback.

no-unstable-deps applies to these hooks

The no-unstable-deps rule applies to these query hooks whose return objects are not referentially stable: useQuery, useSuspenseQuery, useQueries, useSuspenseQueries, useInfiniteQuery, useSuspenseInfiniteQuery, and useMutation.

no-unstable-deps fix: destructure instead of passing whole object

Instead of passing the entire object returned from a query hook to a React hook dependency array, destructure the return value and pass only the destructured values you need into the dependency array.

no-unstable-deps correct example

const { mutate } = useMutation({ mutationFn: (value: string) => value }) const callback = useCallback(() => { mutate('hello') }, [mutate]) This is correct because the mutate function is destructured from the mutation object and only the stable mutate function is passed to the useCallback dependency array.

no-unstable-deps recommended status

The no-unstable-deps rule is marked as recommended.

no-unstable-deps fixable status

The no-unstable-deps rule is not fixable (no auto-fix available).

no-void-query-fn correct example

This code follows the no-void-query-fn rule by returning the fetched data: const query = useQuery({ queryKey: ['todos'], queryFn: async () => { const todos = await api.todos.fetch() return todos }, })

no-void-query-fn incorrect example

This code violates the no-void-query-fn rule because the queryFn does not return the fetched data: const query = useQuery({ queryKey: ['todos'], queryFn: async () => { await api.todos.fetch() // Function doesn't return the fetched data }, })

no-void-query-fn rule purpose

The no-void-query-fn ESLint rule disallows returning void from query functions. Query functions must return a value that will be cached by TanStack Query. Functions that don't return a value can lead to unexpected behavior and might indicate a mistake in the implementation.

no-void-query-fn is not fixable

The no-void-query-fn ESLint rule is not fixable, meaning ESLint cannot automatically correct violations of this rule.

no-void-query-fn is recommended

The no-void-query-fn rule is marked as recommended for inclusion in ESLint configs.

prefer-query-options correct pattern: queryKey from queryOptions in QueryClient methods

QueryClient methods should access the queryKey from the queryOptions result, such as queryClient.getQueryData(todoOptions(id).queryKey) or queryClient.invalidateQueries({ queryKey: todoOptions(id).queryKey }).

prefer-query-options rule purpose

The prefer-query-options ESLint rule enforces the use of queryOptions or infiniteQueryOptions to co-locate queryKey and queryFn. Separating queryKey and queryFn can cause unexpected runtime issues when the same query key is accidentally used with more than one queryFn. Using queryOptions makes queries safer and easier to reuse.

prefer-query-options ESLint rule configuration

The prefer-query-options rule is recommended (strict) and is not fixable. It has rule id @tanstack/query/prefer-query-options.

prefer-query-options incorrect pattern: queryKey and queryFn separated in useQuery

The rule flags queryKey and queryFn passed separately to useQuery as incorrect, whether inside a component or in a custom hook.

prefer-query-options correct pattern: use queryOptions wrapper

To satisfy the rule, wrap queryKey and queryFn in queryOptions and pass the result to useQuery. The queryOptions can be defined in a separate function like getFooOptions(id) and reused by calling useQuery(getFooOptions(id)). Additional options like select can be spread or passed alongside the queryOptions result.

prefer-query-options enforces queryKey reuse from queryOptions

The rule also enforces that queryKey values must be reused from a queryOptions result rather than typed manually. When using QueryClient methods like getQueryData or invalidateQueries, the queryKey must come from the queryOptions object, accessed as queryOptions(id).queryKey, instead of being hardcoded as a literal array.

prefer-query-options incorrect pattern: hardcoded queryKey in QueryClient methods

Using hardcoded queryKey literals in QueryClient methods like queryClient.getQueryData(['todo', id]) or queryClient.invalidateQueries({ queryKey: ['todo', id] }) is flagged as incorrect when a corresponding queryOptions exists.

stable-query-client incorrect pattern

Creating a new QueryClient directly inside a component function is incorrect. This causes a new instance to be created on every render. Example: function App() { const queryClient = new QueryClient() }

stable-query-client correct pattern with module-level constant

Another correct way to create a QueryClient is to define it as a module-level constant outside the component: const queryClient = new QueryClient(), then pass it to the provider. This ensures only one instance exists.

stable-query-client correct pattern with async Server Component

Creating a QueryClient inside an async Server Component is correct: async function App() { const queryClient = new QueryClient() }. The async function is only called once on the server, so no multiple instances are created.

stable-query-client ESLint rule is recommended

The stable-query-client ESLint rule is marked as recommended and should be enabled.

stable-query-client ESLint rule is fixable

The stable-query-client ESLint rule is fixable, meaning ESLint can automatically fix violations of this rule.

stable-query-client ESLint rule purpose

The stable-query-client ESLint rule enforces that a QueryClient should only be created once for the lifecycle of your application, not a new instance on every render. This is because the QueryClient contains the QueryCache, and creating multiple instances wastes resources and breaks cache sharing.

stable-query-client ESLint rule exception

It is allowed to create a new QueryClient inside an async Server Component, because the async function is only called once on the server, so multiple instances are not created on each render.

stable-query-client correct pattern with useState

One correct way to create a QueryClient is to initialize it once using useState with a callback: const [queryClient] = useState(() => new QueryClient()). This ensures a single instance is created and persists across renders.

Give your agent this brain