Skip to content

fix(svelte-query): untrack observer subscription - #11542

Open
gzuuus wants to merge 1 commit into
TanStack:mainfrom
gzuuus:fix/svelte-query-untrack-subscribe
Open

gzuuus wants to merge 1 commit into
TanStack:mainfrom
gzuuus:fix/svelte-query-untrack-subscribe

Conversation

@gzuuus

@gzuuus gzuuus commented Sep 18, 2026

Copy link
Copy Markdown

Fixes #11541

🎯 Changes

For queries without cached data, queryFn executes synchronously inside the subscription $effect (onSubscribeshouldFetchOnMountfetch). Any reactive state read by queryFn before its first await was therefore recorded as a dependency of that effect. Writes to that state tore the observer down (cancelling the in-flight fetch) and re-subscribed — and since the query stays dataless, every re-subscription starts a new fetch: an unbounded teardown/refetch loop. See #11541 for the full mechanism and the exact set of conditions that sustain it.

The fix keeps the observer read tracked, but runs the subscription itself inside untrack():

$effect(() => {
  const o = observer
  const unsubscribe = isRestoring.current
    ? () => undefined
    : untrack(() => o.subscribe(() => update(createResult())))
  return unsubscribe
})

Note: the naive untrack(() => observer.subscribe(...)) is not enough — it also untracks the observer read, so changing queries would no longer re-subscribe. The existing test “should track queries added to an initially empty array” catches that case, which is why the observer read stays outside the untrack().

Applied to both createBaseQuery.svelte.ts (also covers createInfiniteQuery) and createQueries.svelte.ts.

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested code changes locally with pnpm run test:pr, or these tests do not apply to this pull request.
  • I fully understand the code in this pull request, including any code generated with AI assistance.

🚀 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

    • Prevented reactive state updates during an in-flight query from unnecessarily restarting subscriptions or cancelling fetches.
    • Query functions that read state before their first asynchronous wait now complete reliably without repeated refetches.
  • Tests

    • Added regression coverage for single-fetch behavior and successful completion in both individual and multiple-query scenarios.

For queries without cached data, queryFn executes synchronously inside
the subscription effect (onSubscribe -> shouldFetchOnMount -> fetch).
Any reactive state read by queryFn before its first await was therefore
recorded as a dependency of that effect: writes to the state tore the
observer down (cancelling the in-flight fetch) and re-subscribed,
starting a new fetch from a still-dataless query — indefinitely.

Keep the observer read tracked so changing options still re-subscribes,
but run the subscription itself inside untrack().
@coderabbitai

coderabbitai Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: b74bcc53-e2d1-41a7-a2b3-236bac9fc3b0

📥 Commits

Reviewing files that changed from the base of the PR and between d346e85 and 23136f4.

📒 Files selected for processing (5)
  • .changeset/svelte-query-untrack-subscribe.md
  • packages/svelte-query/src/createBaseQuery.svelte.ts
  • packages/svelte-query/src/createQueries.svelte.ts
  • packages/svelte-query/tests/createQueries/createQueries.svelte.test.ts
  • packages/svelte-query/tests/createQuery/createQuery.svelte.test.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

The Svelte Query subscription effects now execute observer subscriptions inside untrack(). Observer reads remain tracked. Regression tests cover reactive state access during asynchronous query functions, and a patch changeset records the fix.

Changes

Svelte Query subscription fix

Layer / File(s) Summary
Untrack observer subscriptions
packages/svelte-query/src/createBaseQuery.svelte.ts, packages/svelte-query/src/createQueries.svelte.ts
Both subscription effects capture the observer before calling subscribe inside untrack().
Regression coverage and release metadata
packages/svelte-query/tests/createQuery/createQuery.svelte.test.ts, packages/svelte-query/tests/createQueries/createQueries.svelte.test.ts, .changeset/svelte-query-untrack-subscribe.md
Tests verify one fetch and successful completion when query functions read and update reactive state during asynchronous execution. The changeset records a patch release for @tanstack/svelte-query.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: Medium

Suggested reviewers: sukvvon

Merge Risk: ⚪ Minimal · up to 23136

The subscription change preserves query reactivity and cleanup while preventing reactive query-function reads from causing refetch loops. No actionable merge-blocking risk remains.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: running the Svelte Query observer subscription inside untrack().
Description check ✅ Passed The description includes all required template sections, explains the motivation and implementation, confirms testing and contributor checks, and documents the changeset release impact.
Linked Issues check ✅ Passed The change meets the coding requirements in [#11541]. createBaseQuery.svelte.ts reads observer into o before it runs o.subscribe(...) inside untrack(). The observer read remains tracked, so …
Out of Scope Changes check ✅ Passed The changeset, source changes, explanatory comments, and regression tests directly support [#11541]. The pull request changes only observer subscription tracking for the affected Svelte Query paths. N…
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 2 functions across 4 files. (1 skipped: 1 …
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR

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.

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.

createQuery/createQueries: a queryFn that reads reactive state before its first await causes an infinite observer teardown/refetch loop

1 participant