fix(svelte-query): release the query observer when an $effect.root is disposed by dvd233 · Pull Request #11611 · TanStack/query · GitHub
Skip to content

fix(svelte-query): release the query observer when an $effect.root is disposed - #11611

Closed
dvd233 wants to merge 1 commit into
TanStack:mainfrom
dvd233:fix/svelte-query-effect-root-cleanup-11587
Closed

dvd233 wants to merge 1 commit into
TanStack:mainfrom
dvd233:fix/svelte-query-effect-root-cleanup-11587

Conversation

@dvd233

@dvd233 dvd233 commented Sep 24, 2026 •

Copy link
Copy Markdown

What

Fixes #11587 — after #9810, a query created inside $effect.root with an explicit QueryClient remains subscribed after the root's disposer runs: interval refetches continue, and a pending request consuming its AbortSignal is never aborted.

Root cause

#9810 replaced the $effect(() => { return unsubscribe }) form with an eager observer.subscribe(...) (needed for server and suspended branches) plus watchChanges for resubscription, and made onDestroy the only teardown. But:

  • onDestroy does not run in non-component $effect.root contexts (the surrounding try/catch swallows the failure), and
  • when the client/restoration subscription never changes, watchChanges never resubscribes — its first run registers no cleanup,

so in an $effect.root nothing ever unsubscribes the eager subscription. Component unmount is unaffected because onDestroy works there.

Fix

Register the teardown with the enclosing effect root's disposal as well — runEffect('pre', () => () => unsubscribe()) — keeping onDestroy for the server path ($effect.pre does not run during SSR) and components. runEffect is now exported from utils.svelte.ts. Calling unsubscribe twice is a no-op, so the component path is unchanged.

Verification

  • Red: the issue's linked regression test fails on current main — 10 failed, 2 passed (observer count stays 1, cache entry survives gcTime, pending request not aborted, interval refetches continue).
  • Green: 12/12 pass with the fix.
  • Full @tanstack/svelte-query suite: 24 files / 226 tests, all pass (includes the new file).
  • svelte-check: identical 4 errors / 48 warnings before and after (pre-existing tooling issues: root.eslint.config.js module resolution and RegExp.config types, both in untouched files); ESLint on changed files: 0 errors; changeset included.

The regression test is @Osraka's, from the branch linked in the issue.

Summary by CodeRabbit

  • Bug Fixes
    • Query observers are now reliably cleaned up when their reactive scope is disposed, including outside component lifecycles. This prevents unnecessary background refetching and allows unused queries to be removed from the cache after the configured retention period. Component unmounts and changes to the selected query client also clean up subscriptions correctly.

… disposed

After TanStack#9810, a query created inside $effect.root with an explicit
QueryClient stays subscribed after the root's disposer runs: interval
refetches continue, and a pending request's AbortSignal is never
aborted. The eager observer.subscribe() from the async-Svelte rework
only had onDestroy() for teardown, which does not run in non-component
effect roots; when the client/restoration subscription never changes,
watchChanges never resubscribes either, so nothing ever unsubscribes.

Register the teardown with the enclosing effect root's disposal as
well (via runEffect, now exported), keeping onDestroy for the server
and component paths. Component unmount behavior is unchanged; double
unsubscribe is a no-op.

Regression test by @Osraka from the issue's linked branch.

Fixes TanStack#11587
@coderabbitai

coderabbitai Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

@DogPawHat DogPawHat closed this Oct 8, 2026
@DogPawHat

Copy link
Copy Markdown
Collaborator

@DogPawHat DogPawHat added the automated PR author detected as automated label Oct 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

automated PR author detected as automated

Projects

None yet

Development

Successfully merging this pull request may close these issues.

svelte-query: query subscriptions survive effect-root disposal after async Svelte update

2 participants