Skip to content

Conversation

@bartlangelaan
Copy link

@bartlangelaan bartlangelaan commented Oct 22, 2025

🎯 Changes

This is an attempt to solve issue #9798.

It adds an query observer when running fetchQuery, to make sure the query is not cancelled if all other observers are unsubscribed.

I am unfamiliar with this codebase, and at least one test is still failing. I am unsure if this is the right direction to fix the issue. If anyone wants to take over this PR, feel free to do so.

✅ 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
    • Fixed an issue where fetchQuery would cancel in-progress queries when other observers unsubscribed. Queries now continue to completion as expected.

@changeset-bot
Copy link

changeset-bot bot commented Oct 22, 2025

🦋 Changeset detected

Latest commit: ebe1535

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 19 packages
Name Type
@tanstack/query-core Patch
@tanstack/angular-query-experimental Patch
@tanstack/query-async-storage-persister Patch
@tanstack/query-broadcast-client-experimental Patch
@tanstack/query-persist-client-core Patch
@tanstack/query-sync-storage-persister Patch
@tanstack/react-query Patch
@tanstack/solid-query Patch
@tanstack/svelte-query Patch
@tanstack/vue-query Patch
@tanstack/angular-query-persist-client Patch
@tanstack/react-query-persist-client Patch
@tanstack/solid-query-persist-client Patch
@tanstack/svelte-query-persist-client Patch
@tanstack/react-query-devtools Patch
@tanstack/react-query-next-experimental Patch
@tanstack/solid-query-devtools Patch
@tanstack/svelte-query-devtools Patch
@tanstack/vue-query-devtools Patch

Not sure what this means? Click here to learn what changesets are.

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 22, 2025

Walkthrough

The PR modifies query cancellation semantics in TanStack Query. When fetchQuery detects stale data, it now creates and attaches a QueryObserver to manage the fetch lifecycle instead of directly fetching. This prevents the query from being cancelled if other observers unsubscribe during the fetch. Tests verify the new observer-based flow.

Changes

Cohort / File(s) Summary
Changeset
.changeset/petite-towns-rule.md
Documents patch bump for @tanstack/query-core with behavior change: fetchQuery no longer cancels when other observers unsubscribe
Query-core Implementation
packages/query-core/src/queryClient.ts
Reworked fetchQuery to use QueryObserver lifecycle management when data is stale; added QueryObserver import; replaced direct fetch logic with observer-managed fetch and cleanup
Query-core Tests
packages/query-core/src/__tests__/query.test.tsx
Updated cancellation tests: replaced prefetchQuery with QueryObserver setup; added signal.throwIfAborted() to test queryFn; adjusted expectations for observer-driven cancellation flow
React-query Tests
packages/react-query/src/__tests__/useQuery.test.tsx
Added test verifying fetchQuery is not cancelled when useQuery component unmounts; added queryOptions import for test configuration

Sequence Diagram

sequenceDiagram
    participant Client as queryClient.fetchQuery
    participant Observer as QueryObserver
    participant Query as Query
    participant Cache as Cache

    Client->>Client: Check if data is stale
    alt Data is stale
        Client->>Observer: Create and attach observer
        Observer->>Query: Subscribe to query
        Client->>Query: Execute fetch
        Query->>Cache: Update data
        Client->>Observer: Remove observer (finally)
        Note over Client,Cache: Query remains in cache<br/>not cancelled by other<br/>observer unsubscribes
    else Data is fresh
        Client->>Cache: Return cached data
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

The changes introduce logic modifications to fetchQuery's control flow with observer lifecycle management, span multiple test files with varying adjustments, and require understanding interactions between QueryObserver and cancellation semantics. Changes are moderately heterogeneous across implementation and tests.

Possibly related PRs

Suggested reviewers

  • TkDodo

Poem

🐰 Fetch queries now observe with care,
No cancellation through the air,
When observers part their ways,
The fetch persists through all its days,
A steadfast rabbit's gift so rare! 🥕

Pre-merge checks and finishing touches

❌ 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%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly and accurately summarizes the primary change by stating that fetchQuery should no longer be cancelled when a useQuery component unmounts, directly reflecting the intent and scope of the PR without extraneous detail.
Description Check ✅ Passed The PR description follows the repository’s template by providing a “## 🎯 Changes” section that explains the addition of a query observer to prevent fetchQuery cancellation, a fully completed “## ✅ Checklist” confirming guideline adherence and local testing, and a “## 🚀 Release Impact” section indicating the inclusion of a changeset and its effect on published code.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

@bartlangelaan bartlangelaan marked this pull request as ready for review October 22, 2025 21:09
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: 0

🧹 Nitpick comments (4)
.changeset/petite-towns-rule.md (1)

5-5: Tighten wording and format the API name.

Suggest: “When running queryClient.fetchQuery, the query will no longer be cancelled if other observers unsubscribe.” This adds code formatting, active voice, and the more common “unsubscribe.”

packages/query-core/src/queryClient.ts (1)

366-379: Using a temporary QueryObserver to prevent cancellation is sound; add small polish.

  • Behavior change is correct: keeping one observer prevents “last-unsubscribe” cancellation while fetchQuery is in flight. LGTM.
  • Minor: adding the observer via query.addObserver(observer) (without listeners) can still emit cache notifications from observer.updateResult(); acceptable, but worth noting for large subscriber counts.
  • Optional polish: keep style consistent and use a clearer try/finally.
-    const isDataStale = query.isStaleByTime(
-      resolveStaleTime(defaultedOptions.staleTime, query),
-    );
+    const isDataStale = query.isStaleByTime(
+      resolveStaleTime(defaultedOptions.staleTime, query),
+    )
@@
-    const observer = new QueryObserver(this,defaultedOptions)
-    query.addObserver(observer);
-
-    return query.fetch(defaultedOptions).finally(() => query.removeObserver(observer))
+    const observer = new QueryObserver(this, defaultedOptions)
+    query.addObserver(observer)
+    try {
+      return await query.fetch(defaultedOptions)
+    } finally {
+      query.removeObserver(observer)
+    }

Please double‑check that this path preserves existing pause/resume semantics (offline/focus) by running the affected tests for paused retries and onFocus/onOnline refetch.

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

294-305: Timer alignment nit.

The added 10ms advance before unsubscribe makes the race explicit. Consider adding a brief inline comment “start fetch → unsubscribe after 10ms” for clarity.

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

6780-6845: Solid coverage for “don’t cancel fetchQuery on unmount”; fix comment.

The test correctly asserts that the AbortSignal remains un‑aborted and fetchQuery resolves. Minor nit: comment says “after 2 seconds” but timers use ~10–11ms; update the comment to avoid confusion.

-    // This unmounts useQuery after 2 seconds, fetchQuery should continue running
+    // This unmounts useQuery after ~10ms; fetchQuery should continue running
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 27df987 and ebe1535.

📒 Files selected for processing (4)
  • .changeset/petite-towns-rule.md (1 hunks)
  • packages/query-core/src/__tests__/query.test.tsx (3 hunks)
  • packages/query-core/src/queryClient.ts (2 hunks)
  • packages/react-query/src/__tests__/useQuery.test.tsx (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
packages/react-query/src/__tests__/useQuery.test.tsx (3)
packages/react-query/src/queryOptions.ts (1)
  • queryOptions (85-87)
packages/query-core/src/utils.ts (1)
  • sleep (363-367)
packages/react-query/src/__tests__/utils.tsx (1)
  • renderWithClient (9-23)
packages/query-core/src/__tests__/query.test.tsx (1)
packages/query-core/src/utils.ts (1)
  • sleep (363-367)
packages/query-core/src/queryClient.ts (3)
packages/query-core/src/queryObserver.ts (2)
  • query (704-721)
  • QueryObserver (41-747)
packages/query-core/src/utils.ts (1)
  • resolveStaleTime (101-113)
packages/query-core/src/queriesObserver.ts (1)
  • observer (263-269)
🔇 Additional comments (3)
packages/query-core/src/queryClient.ts (1)

16-16: Import looks correct and scoped to core.
No concerns.

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

216-238: Good AbortSignal consumption check.

Adding signal.throwIfAborted() and asserting resolved data validates the new non‑cancellation behavior of fetchQuery.

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

16-16: queryOptions export confirmed
packages/react-query/src/index.ts line 20 re-exports queryOptions — no further action needed.

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.

1 participant