Skip to content

fix(eslint-plugin-query): track custom query hook wrappers#10730

Open
junjuny0227 wants to merge 3 commits into
TanStack:mainfrom
junjuny0227:fix/no-unstable-deps-custom-hooks
Open

fix(eslint-plugin-query): track custom query hook wrappers#10730
junjuny0227 wants to merge 3 commits into
TanStack:mainfrom
junjuny0227:fix/no-unstable-deps-custom-hooks

Conversation

@junjuny0227
Copy link
Copy Markdown

@junjuny0227 junjuny0227 commented May 19, 2026

🎯 Changes

Fixes #10728.

Updates no-unstable-deps to track simple same-file custom hooks that directly return a TanStack Query hook result. This makes the rule report wrapped unstable query or mutation results when they are used directly in React dependency arrays.

The new coverage includes direct wrappers such as:

const useMyMutation = () => useMutation(...)
const mutation = useMyMutation()

and function declarations that directly return useQuery(...).

Validation run locally:

  • npx nx run @tanstack/eslint-plugin-query:test:lib
  • pnpm --filter @tanstack/eslint-plugin-query run test:eslint
  • pnpm --filter @tanstack/eslint-plugin-query run test:types:tscurrent

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm run test:pr.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

Summary by CodeRabbit

  • Bug Fixes

    • no-unstable-deps now detects unstable TanStack Query results returned through custom wrapper hooks as well as direct hook calls, preventing unstable values in React dependency arrays.
  • Tests

    • Added invalid test cases covering wrapper hooks declared before and after use for both query and mutation results.
  • Refactor

    • Refactored detection/resolution to track custom hooks while preserving existing special-case handling for combined queries.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 19, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d81ea61c-e8c8-4a73-bbc3-e8b5575c4305

📥 Commits

Reviewing files that changed from the base of the PR and between 43e744b and 69acee7.

📒 Files selected for processing (2)
  • packages/eslint-plugin-query/src/__tests__/no-unstable-deps.test.ts
  • packages/eslint-plugin-query/src/rules/no-unstable-deps/no-unstable-deps.rule.ts

📝 Walkthrough

Walkthrough

The rule now tracks custom hooks that return TanStack Query hook results, resolves variables initialized from those custom hooks, and flags such variables when used in React hook dependency arrays. Tests were added for wrapper hooks defined before and after usage for useMutation and useQuery.

Changes

Custom hook wrapper detection for no-unstable-deps

Layer / File(s) Summary
Custom hook tracking infrastructure
packages/eslint-plugin-query/src/rules/no-unstable-deps/no-unstable-deps.rule.ts
trackedCustomHooks map and queuing state store potential custom-hook -> query-hook mappings and defer processing until Program:exit. isCustomHookName() classifies use[A-Z0-9] style custom hooks.
Query hook resolution helpers
packages/eslint-plugin-query/src/rules/no-unstable-deps/no-unstable-deps.rule.ts
Refactors detection into helpers that identify direct TanStack query hooks (special-casing useQueries + combine), resolve call expressions to direct or tracked custom hooks, and extract returned query hooks from custom hook bodies.
AST visitor integration and collection
packages/eslint-plugin-query/src/rules/no-unstable-deps/no-unstable-deps.rule.ts
Registers custom hooks from FunctionDeclaration and function/arrow initializers, queues call-expression initializers for later resolution, and defers dependency-array validation to Program:exit using collected tracked variables.
Test coverage for wrapper hooks
packages/eslint-plugin-query/src/__tests__/no-unstable-deps.test.ts
Adds invalid tests for wrapper hooks returning useMutation and useQuery, each declared before and after usage, asserting noUnstableDeps warnings with correct queryHook values.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • TanStack/query#10642: Overlapping changes to no-unstable-deps handling for useSuspenseQueries/combine logic.

Suggested labels

package: eslint-plugin-query

Suggested reviewers

  • TkDodo

Poem

I'm a rabbit in the linting glade,
I sniff the hooks that devs have made,
Wrapped queries hidden in disguise,
I flag the unstable ones — surprise! 🐰🔎

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix(eslint-plugin-query): track custom query hook wrappers' clearly and concisely describes the main change in the changeset.
Description check ✅ Passed The PR description covers the main changes, includes the fixed issue reference, provides validation steps, and completes the checklist requirements.
Linked Issues check ✅ Passed The changes directly address issue #10728 by implementing tracking of custom hook wrappers that return TanStack Query hooks for the no-unstable-deps rule.
Out of Scope Changes check ✅ Passed All changes are focused on extending the no-unstable-deps rule to track custom hook wrappers and related test coverage, with no unrelated modifications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@junjuny0227 junjuny0227 marked this pull request as ready for review May 19, 2026 01:35
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/eslint-plugin-query/src/__tests__/no-unstable-deps.test.ts (1)

166-209: ⚡ Quick win

Add a declaration-order regression test for wrapper hooks

Please add an invalid case where Component appears before function useMyQuery/useMyMutation and still expects noUnstableDeps. This guards the new wrapper-tracking contract against AST-order regressions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/eslint-plugin-query/src/__tests__/no-unstable-deps.test.ts` around
lines 166 - 209, Add an invalid test case to guard against AST-order regressions
by moving the Component declaration before the wrapper hook definitions (for
both useMyMutation and useMyQuery scenarios) while keeping the same expectation:
an error with messageId 'noUnstableDeps' and data { reactHook: reactHookAlias,
queryHook: 'useMutation' } or { queryHook: 'useQuery' } respectively; update the
test entries in no-unstable-deps.test.ts to include these variants so the rule
flags using the wrapper hook result as a dependency even when the wrapper is
declared later in the file (referencing useMyMutation, useMyQuery, Component and
the existing reactHookInvocation/ reactHookAlias symbols).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/eslint-plugin-query/src/rules/no-unstable-deps/no-unstable-deps.rule.ts`:
- Around line 177-186: The rule misses wrappers declared later in the file
because trackedCustomHooks is filled during FunctionDeclaration visits while
variable-initializer resolution runs in the same traversal, so calls earlier in
the AST see an unresolved queryHook; to fix, delay resolving
VariableDeclarator/variable-init wrappers until after all FunctionDeclaration
nodes are recorded (e.g. move the variable-init resolution logic into
Program:exit or perform a second pass after collecting trackedCustomHooks) so
getReturnedQueryHook and trackedCustomHooks contain all declarations before
resolving wrappers like the code in the VariableDeclarator resolution block.

---

Nitpick comments:
In `@packages/eslint-plugin-query/src/__tests__/no-unstable-deps.test.ts`:
- Around line 166-209: Add an invalid test case to guard against AST-order
regressions by moving the Component declaration before the wrapper hook
definitions (for both useMyMutation and useMyQuery scenarios) while keeping the
same expectation: an error with messageId 'noUnstableDeps' and data { reactHook:
reactHookAlias, queryHook: 'useMutation' } or { queryHook: 'useQuery' }
respectively; update the test entries in no-unstable-deps.test.ts to include
these variants so the rule flags using the wrapper hook result as a dependency
even when the wrapper is declared later in the file (referencing useMyMutation,
useMyQuery, Component and the existing reactHookInvocation/ reactHookAlias
symbols).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 99787a89-106c-4f58-a958-fd3a10bfb9fb

📥 Commits

Reviewing files that changed from the base of the PR and between 57f8ec7 and 16050b3.

📒 Files selected for processing (2)
  • packages/eslint-plugin-query/src/__tests__/no-unstable-deps.test.ts
  • packages/eslint-plugin-query/src/rules/no-unstable-deps/no-unstable-deps.rule.ts

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/eslint-plugin-query/src/__tests__/no-unstable-deps.test.ts`:
- Line 222: The test defines useMyMutation with a const (const useMyMutation =
() => useMutation(...)) but calls it earlier, causing a TDZ ReferenceError;
replace the const arrow function with a function declaration (function
useMyMutation() { return useMutation({ mutationFn: (value: string) => value });
}) so the wrapper is hoisted and the test remains runtime-valid; update the
symbol useMyMutation in the test accordingly to match the other valid test
cases.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d2cef624-7685-40a4-9c19-3f8bec611f26

📥 Commits

Reviewing files that changed from the base of the PR and between 16050b3 and aa2d019.

📒 Files selected for processing (2)
  • packages/eslint-plugin-query/src/__tests__/no-unstable-deps.test.ts
  • packages/eslint-plugin-query/src/rules/no-unstable-deps/no-unstable-deps.rule.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/eslint-plugin-query/src/rules/no-unstable-deps/no-unstable-deps.rule.ts

Comment thread packages/eslint-plugin-query/src/__tests__/no-unstable-deps.test.ts Outdated
@TkDodo
Copy link
Copy Markdown
Collaborator

TkDodo commented May 22, 2026

@Newbie012 can you take a look please

@junjuny0227 junjuny0227 force-pushed the fix/no-unstable-deps-custom-hooks branch from 43e744b to 69acee7 Compare May 24, 2026 12:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

@tanstack/query/no-unstable-deps does not flag custom hooks that wrap and return useMutation/useQuery

2 participants