Skip to content

Conversation

TkDodo
Copy link
Collaborator

@TkDodo TkDodo commented Oct 8, 2025

what a chore

Summary by CodeRabbit

  • Bug Fixes

    • Improved hydration reliability by syncing options after render, reducing edge-case issues during initial hydration.
    • Corrected mutation state initialization to avoid false negatives on first render, ensuring accurate state tracking.
  • Chores

    • Updated React Hooks ESLint plugin to the latest stable version.
    • Aligned projects with the latest recommended React Hooks lint rules for compatibility and consistency.
    • Cleaned up lint directives and test annotations without affecting runtime behavior.

Copy link

changeset-bot bot commented Oct 8, 2025

⚠️ No Changeset found

Latest commit: 35b8f89

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link

nx-cloud bot commented Oct 8, 2025

View your CI Pipeline Execution ↗ for commit 35b8f89

Command Status Duration Result
nx affected --targets=test:sherif,test:knip,tes... ✅ Succeeded 4m 30s View ↗
nx run-many --target=build --exclude=examples/*... ✅ Succeeded 1m 23s View ↗

☁️ Nx Cloud last updated this comment at 2025-10-08 11:28:50 UTC

Copy link
Contributor

coderabbitai bot commented Oct 8, 2025

Walkthrough

ESLint configs across examples and packages were updated to migrate React Hooks plugin usage to recommended-latest, adjust rules, and remove legacy/react-compiler settings. Several lint directives in source and tests were added/updated. Two functional tweaks landed: optionsRef sync moved to useEffect in HydrationBoundary, and a null-based init guard in useMutationState.

Changes

Cohort / File(s) Summary
Examples: React ESLint cleanup
examples/react/algolia/eslint.config.js, examples/react/basic/eslint.config.js, examples/react/basic-graphql-request/eslint.config.js, examples/react/shadow-dom/eslint.config.js
Removed eslint-plugin-react-hooks import and config; dropped reactHooks.configs.recommended and inline react-hooks/* rules from exports.
Packages: ESLint react-hooks migration
packages/react-query/eslint.config.js, packages/react-query-devtools/eslint.config.js, packages/react-query-next-experimental/eslint.config.js, packages/react-query-persist-client/eslint.config.js
Switched to default import for react-hooks plugin; replaced configs.recommended with configs['recommended-latest'] (with ts-expect-error); removed react-hooks/react-compiler; added react-hooks/unsupported-syntax and react-hooks/incompatible-library; retained exhaustive-deps and rules-of-hooks; minor additional rule tweaks in react-query tests block.
Functional: Hydration timing
packages/react-query/src/HydrationBoundary.tsx
Moved optionsRef.current assignment from render to useEffect; added targeted ESLint disable comments.
Functional: Mutation state init guard
packages/react-query/src/useMutationState.ts
Changed initialization check from !result.current to result.current === null.
Lint directives in source/tests
packages/react-query-next-experimental/src/HydrationStreamProvider.tsx, packages/react-query-persist-client/src/__tests__/PersistQueryClientProvider.test.tsx, packages/react-query/src/__tests__/useQuery.test.tsx, packages/react-query/src/__tests__/useSuspenseInfiniteQuery.test.tsx, packages/react-query/src/__tests__/useSuspenseQueries.test.tsx, packages/react-query/src/__tests__/useSuspenseQuery.test.tsx, packages/react-query/src/__tests__/useSuspenseQueries.test-d.tsx
Updated/added ESLint disable comments (immutability/purity), removed one disable in a test, adjusted a test name string, and modified a ref-init pattern in one test (null sentinel). No runtime logic changes except the ref-init in the test.
Dependency bump
package.json
Upgraded eslint-plugin-react-hooks from ^6.0.0-rc.2 to ^6.1.1.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as App Component
  participant HB as HydrationBoundary
  participant QC as QueryClient

  U->>HB: Render with props.options
  note over HB: optionsRef.current not set during render
  HB-->>U: Render output
  rect rgb(236,248,255)
  note right of HB: After commit (useEffect)
  HB->>HB: optionsRef.current = props.options
  HB->>QC: hydrate(newQueries, optionsRef.current)
  QC-->>HB: Hydration complete
  end
Loading
sequenceDiagram
  autonumber
  actor C as Component
  participant HMS as useMutationState
  participant MC as MutationCache

  C->>HMS: Call hook
  alt Initial render
    HMS->>HMS: if (result.current === null) init from MC
  end
  HMS->>MC: subscribe(listener)
  MC-->>HMS: notify on mutation changes
  HMS-->>C: Return current result
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

package: eslint-plugin-query

Suggested reviewers

  • arnoud-dv

Poem

I twitch my whiskers, configs align,
From hooks of old to latest design.
Options now wait till effects take hold,
Mutations check for null—so bold.
With lint soothed quiet, I hop with glee,
Carrots, commits, and CI green for me! 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The PR description consists only of the phrase “what a chore” and does not follow the repository’s required template sections for describing changes, confirming testing steps, or indicating release impact. Without the “🎯 Changes,” “✅ Checklist,” and “🚀 Release Impact” headings and content, reviewers lack critical information about the modifications, testing, and release considerations. As a result, it fails to meet the description guidelines. Please update the PR description to include the required “🎯 Changes,” “✅ Checklist,” and “🚀 Release Impact” sections from the template, detailing what was changed and why, confirming that tests were run, and specifying any release implications.
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The title clearly summarizes the main change of updating the eslint-plugin-react-hooks dependency and related configurations across the repository. It is concise, follows conventional commit style, and provides enough context for reviewers to understand the primary purpose of the pull request. Therefore it meets the repository’s title guidelines.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/eslint-plugin-react-hooks

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

Copy link

pkg-pr-new bot commented Oct 8, 2025

More templates

@tanstack/angular-query-experimental

npm i https://pkg.pr.new/@tanstack/angular-query-experimental@9737

@tanstack/eslint-plugin-query

npm i https://pkg.pr.new/@tanstack/eslint-plugin-query@9737

@tanstack/query-async-storage-persister

npm i https://pkg.pr.new/@tanstack/query-async-storage-persister@9737

@tanstack/query-broadcast-client-experimental

npm i https://pkg.pr.new/@tanstack/query-broadcast-client-experimental@9737

@tanstack/query-core

npm i https://pkg.pr.new/@tanstack/query-core@9737

@tanstack/query-devtools

npm i https://pkg.pr.new/@tanstack/query-devtools@9737

@tanstack/query-persist-client-core

npm i https://pkg.pr.new/@tanstack/query-persist-client-core@9737

@tanstack/query-sync-storage-persister

npm i https://pkg.pr.new/@tanstack/query-sync-storage-persister@9737

@tanstack/react-query

npm i https://pkg.pr.new/@tanstack/react-query@9737

@tanstack/react-query-devtools

npm i https://pkg.pr.new/@tanstack/react-query-devtools@9737

@tanstack/react-query-next-experimental

npm i https://pkg.pr.new/@tanstack/react-query-next-experimental@9737

@tanstack/react-query-persist-client

npm i https://pkg.pr.new/@tanstack/react-query-persist-client@9737

@tanstack/solid-query

npm i https://pkg.pr.new/@tanstack/solid-query@9737

@tanstack/solid-query-devtools

npm i https://pkg.pr.new/@tanstack/solid-query-devtools@9737

@tanstack/solid-query-persist-client

npm i https://pkg.pr.new/@tanstack/solid-query-persist-client@9737

@tanstack/svelte-query

npm i https://pkg.pr.new/@tanstack/svelte-query@9737

@tanstack/svelte-query-devtools

npm i https://pkg.pr.new/@tanstack/svelte-query-devtools@9737

@tanstack/svelte-query-persist-client

npm i https://pkg.pr.new/@tanstack/svelte-query-persist-client@9737

@tanstack/vue-query

npm i https://pkg.pr.new/@tanstack/vue-query@9737

@tanstack/vue-query-devtools

npm i https://pkg.pr.new/@tanstack/vue-query-devtools@9737

commit: 35b8f89

Copy link
Contributor

github-actions bot commented Oct 8, 2025

Sizes for commit 35b8f89:

Branch Bundle Size
Main
This PR

Copy link

codecov bot commented Oct 8, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 45.51%. Comparing base (9f82d8e) to head (35b8f89).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##             main    #9737   +/-   ##
=======================================
  Coverage   45.51%   45.51%           
=======================================
  Files         196      196           
  Lines        8323     8324    +1     
  Branches     1894     1898    +4     
=======================================
+ Hits         3788     3789    +1     
  Misses       4093     4093           
  Partials      442      442           
Components Coverage Δ
@tanstack/angular-query-experimental 93.85% <ø> (ø)
@tanstack/eslint-plugin-query 83.03% <ø> (ø)
@tanstack/query-async-storage-persister 43.85% <ø> (ø)
@tanstack/query-broadcast-client-experimental 24.39% <ø> (ø)
@tanstack/query-codemods 0.00% <ø> (ø)
@tanstack/query-core 97.48% <ø> (ø)
@tanstack/query-devtools 3.48% <ø> (ø)
@tanstack/query-persist-client-core 79.60% <ø> (ø)
@tanstack/query-sync-storage-persister 84.61% <ø> (ø)
@tanstack/query-test-utils 77.77% <ø> (ø)
@tanstack/react-query 96.01% <100.00%> (+0.01%) ⬆️
@tanstack/react-query-devtools 10.00% <ø> (ø)
@tanstack/react-query-next-experimental ∅ <ø> (∅)
@tanstack/react-query-persist-client 100.00% <ø> (ø)
@tanstack/solid-query 78.06% <ø> (ø)
@tanstack/solid-query-devtools ∅ <ø> (∅)
@tanstack/solid-query-persist-client 100.00% <ø> (ø)
@tanstack/svelte-query ∅ <ø> (∅)
@tanstack/svelte-query-devtools ∅ <ø> (∅)
@tanstack/svelte-query-persist-client ∅ <ø> (∅)
@tanstack/vue-query 71.10% <ø> (ø)
@tanstack/vue-query-devtools ∅ <ø> (∅)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/react-query/src/__tests__/useQuery.test.tsx (1)

6687-6688: Remove or correct invalid ESLint disable comment
The rule react-hooks/immutability isn’t defined in your ESLint config; update to a valid rule or remove the comment.

🧹 Nitpick comments (1)
packages/react-query/src/HydrationBoundary.tsx (1)

34-36: Add dependency array for efficiency.

The useEffect runs after every render without a dependency array. Consider adding [options] to only update the ref when options actually change:

-React.useEffect(() => {
-  optionsRef.current = options
-})
+React.useEffect(() => {
+  optionsRef.current = options
+}, [options])

This avoids unnecessary effect executions and makes the intent clearer.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9f82d8e and 35b8f89.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (18)
  • examples/react/algolia/eslint.config.js (0 hunks)
  • examples/react/basic-graphql-request/eslint.config.js (0 hunks)
  • examples/react/basic/eslint.config.js (0 hunks)
  • examples/react/shadow-dom/eslint.config.js (0 hunks)
  • package.json (1 hunks)
  • packages/react-query-devtools/eslint.config.js (1 hunks)
  • packages/react-query-next-experimental/eslint.config.js (1 hunks)
  • packages/react-query-next-experimental/src/HydrationStreamProvider.tsx (2 hunks)
  • packages/react-query-persist-client/eslint.config.js (1 hunks)
  • packages/react-query-persist-client/src/__tests__/PersistQueryClientProvider.test.tsx (0 hunks)
  • packages/react-query/eslint.config.js (1 hunks)
  • packages/react-query/src/HydrationBoundary.tsx (2 hunks)
  • packages/react-query/src/__tests__/useQuery.test.tsx (2 hunks)
  • packages/react-query/src/__tests__/useSuspenseInfiniteQuery.test.tsx (1 hunks)
  • packages/react-query/src/__tests__/useSuspenseQueries.test-d.tsx (1 hunks)
  • packages/react-query/src/__tests__/useSuspenseQueries.test.tsx (2 hunks)
  • packages/react-query/src/__tests__/useSuspenseQuery.test.tsx (1 hunks)
  • packages/react-query/src/useMutationState.ts (1 hunks)
💤 Files with no reviewable changes (5)
  • examples/react/algolia/eslint.config.js
  • examples/react/shadow-dom/eslint.config.js
  • packages/react-query-persist-client/src/tests/PersistQueryClientProvider.test.tsx
  • examples/react/basic-graphql-request/eslint.config.js
  • examples/react/basic/eslint.config.js
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Preview
  • GitHub Check: Test
🔇 Additional comments (17)
packages/react-query/src/__tests__/useSuspenseQueries.test.tsx (2)

261-262: LGTM! Appropriate test-specific suppression.

The react-hooks/purity suppression is correct here. The test intentionally uses Math.random() to generate a unique identifier per component instance, verifying that the same component persists across multiple promise resolutions.


669-670: LGTM! Clever pattern for runtime validation testing.

The suppression is appropriate. The Math.random() >= 0 condition is always true at runtime, but TypeScript cannot statically determine this, allowing the test to verify runtime error handling for skipToken without triggering compile-time type errors.

packages/react-query/src/__tests__/useSuspenseInfiniteQuery.test.tsx (1)

28-29: LGTM! Consistent with skipToken validation pattern.

The react-hooks/purity suppression is appropriate. This follows the same pattern as other skipToken tests: using Math.random() >= 0 to create a condition that's always true at runtime but allows testing runtime validation without TypeScript interference.

packages/react-query/src/__tests__/useSuspenseQuery.test.tsx (1)

878-879: LGTM! Consistent skipToken validation testing.

The react-hooks/purity suppression is appropriate and consistent with the pattern used in other test files for validating runtime skipToken error handling.

packages/react-query/src/useMutationState.ts (1)

49-49: LGTM! More explicit initialization guard.

The change from !result.current to result.current === null makes the initialization check more precise and intentional. Since the ref is initialized with null, this explicit equality check avoids potential false positives from other falsy values and clearly communicates that null is the uninitialized sentinel.

packages/react-query/src/HydrationBoundary.tsx (1)

94-95: LGTM! Eslint-disable is justified.

The react-hooks/refs disable is appropriate here. The ref pattern intentionally avoids adding options to the useMemo dependencies to prevent re-computation on options changes (as explained in the comments above).

Note: If client or state changes at the same moment options changes, the useMemo will execute during render with the previous render's options value (before the useEffect on lines 34-36 updates optionsRef.current). Since hydrate is idempotent (line 93 comment), this timing is likely safe, but keep in mind that the options used here may lag by one render in that edge case.

package.json (1)

57-57: LGTM!

The upgrade from RC (^6.0.0-rc.2) to stable (^6.1.1) is appropriate and aligns with the corresponding ESLint config migrations across the repository.

packages/react-query-next-experimental/src/HydrationStreamProvider.tsx (2)

133-133: LGTM!

The lint directive correctly updated from react-hooks/react-compiler to react-hooks/immutability, reflecting the rule changes in eslint-plugin-react-hooks ^6.1.1. The suppression is appropriate for the intentional stream flush via stream.length = 0.


172-172: LGTM!

The lint directive correctly updated from react-hooks/react-compiler to react-hooks/immutability. The suppression is appropriate for the intentional mutation of win[id] to set up the client-side hydration handler.

packages/react-query-devtools/eslint.config.js (3)

4-4: LGTM!

Import style correctly updated to default import, matching the new export structure of eslint-plugin-react-hooks ^6.1.1.


18-19: LGTM!

The new rules unsupported-syntax and incompatible-library are appropriate additions from eslint-plugin-react-hooks ^6.1.1, replacing the previous react-compiler rule.


9-10: Confirm necessity of @ts-expect-error The directive suppresses a type error when spreading reactHooks.configs['recommended-latest']. Verify locally against eslint-plugin-react-hooks@6.1.1 typings and remove the workaround once the upstream type issue is resolved.

packages/react-query-next-experimental/eslint.config.js (1)

4-4: LGTM!

ESLint config changes are consistent with the eslint-plugin-react-hooks ^6.1.1 upgrade: default import, recommended-latest config, and new rules (unsupported-syntax, incompatible-library). The same ts-expect-error type workaround applies here as in other package configs.

Also applies to: 9-10, 18-19

packages/react-query/eslint.config.js (2)

4-4: LGTM!

ESLint config changes are consistent with the eslint-plugin-react-hooks ^6.1.1 upgrade: default import, recommended-latest config, and new rules (unsupported-syntax, incompatible-library).

Also applies to: 9-10, 18-19


25-25: LGTM!

Disabling @eslint-react/dom/no-missing-button-type in test files is reasonable, as test code often uses simplified button elements without explicit types.

packages/react-query-persist-client/eslint.config.js (2)

4-4: Default ES import is correct for eslint-plugin-react-hooks
The plugin’s main entry (index.js) exports via CommonJS, so import reactHooks from 'eslint-plugin-react-hooks' works as expected.


18-19: Plugin v6.1.1 includes both rules The rules react-hooks/unsupported-syntax and react-hooks/incompatible-library are valid in eslint-plugin-react-hooks@^6.1.1.

Comment on lines +9 to +10
// @ts-expect-error wtf
...reactHooks.configs['recommended-latest'],
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Replace unprofessional comment with a clear explanation.

The comment "wtf" is unprofessional and doesn't explain why the type error is being suppressed. Replace it with a clear explanation of the type issue.

-  // @ts-expect-error wtf
+  // @ts-expect-error - recommended-latest config has incomplete type definitions
   ...reactHooks.configs['recommended-latest'],
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// @ts-expect-error wtf
...reactHooks.configs['recommended-latest'],
// @ts-expect-error - recommended-latest config has incomplete type definitions
...reactHooks.configs['recommended-latest'],
🤖 Prompt for AI Agents
In packages/react-query-persist-client/eslint.config.js around lines 9 to 10,
replace the unprofessional "// @ts-expect-error wtf" comment with a concise,
professional explanation of the suppression: state which type incompatibility or
declaration mismatch is happening (e.g., that
reactHooks.configs['recommended-latest'] has an incompatible or missing type
declaration for this project's ESLint config), why the suppression is necessary
(to accept the runtime shape despite TypeScript complaining), and include a TODO
linking to an issue or task to resolve the typing mismatch later; keep it short
and informative.

Comment on lines +6165 to +6168
const mounted = React.useRef<boolean>(null)
// this simulates a synchronous update between the time the query is created
// and the time it is subscribed to that could be missed otherwise
if (!mounted.current) {
if (mounted.current === null) {
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix type annotation for null initialization.

The ref is typed as React.useRef<boolean> but initialized with null. This is a type mismatch that violates TypeScript's type safety.

Apply this diff to correct the type:

-      const mounted = React.useRef<boolean>(null)
+      const mounted = React.useRef<boolean | null>(null)

The usage pattern (initialize with null, then set to true) requires the union type boolean | null to properly represent all possible values the ref can hold.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const mounted = React.useRef<boolean>(null)
// this simulates a synchronous update between the time the query is created
// and the time it is subscribed to that could be missed otherwise
if (!mounted.current) {
if (mounted.current === null) {
const mounted = React.useRef<boolean | null>(null)
// this simulates a synchronous update between the time the query is created
// and the time it is subscribed to that could be missed otherwise
if (mounted.current === null) {
🤖 Prompt for AI Agents
In packages/react-query/src/__tests__/useQuery.test.tsx around lines 6165 to
6168, the ref is declared as React.useRef<boolean> but initialized with null
causing a TypeScript type mismatch; update the ref's generic type to include
null (boolean | null) so it can be initialized with null and later set to true,
e.g., change the declaration to useRef<boolean | null>(null).

export default [
...rootConfig,
reactHooks.configs.recommended,
// @ts-expect-error wtf
Copy link

Choose a reason for hiding this comment

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

drive-by comment as I saw your posts on Bluesky...

if you hadn't already come across it, thought it'd be helpful to point to facebook/react#34745, which explains (some of?) the type issues with integrating the plugin's config (and the expect error overrides you needed).

Issue was closed but the fix hasn't been published yet. You may have a better time with integration once it is 😄

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

ts-expect-error should go away if the types become correct

@TkDodo TkDodo merged commit f4a0cd5 into main Oct 8, 2025
9 checks passed
@TkDodo TkDodo deleted the feature/eslint-plugin-react-hooks branch October 8, 2025 13:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants