Skip to content

fix(solid-query): don't re-create and refetch a removed query - #11360

Open
theRizwan wants to merge 1 commit into
TanStack:solid-query-v6-prefrom
theRizwan:fix/solid-query-removed-query-resurrection
Open

fix(solid-query): don't re-create and refetch a removed query#11360
theRizwan wants to merge 1 commit into
TanStack:solid-query-v6-prefrom
theRizwan:fix/solid-query-removed-query-resurrection

Conversation

@theRizwan

@theRizwan theRizwan commented Sep 1, 2026

Copy link
Copy Markdown

Fixes #11350.

Targets solid-query-v6-pre, since the affected read layer arrived with the 2.0-native rewrite (#11308) and the regression is between 6.0.0-rc.0 and 6.0.0-rc.1. One housekeeping note that follows from that base: GitHub only honours a closing keyword when the PR targets the default branch, so the Fixes above registers as a plain cross-reference and #11350 will need closing by hand on merge.

The bug

Calling queryClient.removeQueries() while a useQuery observer is mounted removes the entry and then immediately brings it back, and the recreated entry starts a fresh fetch of the key that was just removed. A caller that removes entries to make them unreadable, for example an identity boundary that must guarantee the previous user's data cannot be read, cannot make the removal last.

Cause

useBaseQueryLayer has one version signal per hook, bumped by onCacheEvent for any cache event on its hash. 'removed' is such an event, and the recompute it triggers ran through query():

const query = () => {
  version()
  const c = client()
  syncClient(c)
  return c.getQueryCache().build(c, defaultedOptions() as any) as any
}

queryCache.build() creates the entry when the cache does not hold it, so the hook's own removal notification made it rebuild what the caller had just deleted. The resurrection is not passive: the 'added' entry also re-points the still-live observer, and its mount-fetch policy then refetches and repopulates the key. Hence the reported event sequence, removed, added, observerRemoved, observerAdded, updated.

This contradicts the documented contract for the method, that removeQueries "removes matching queries from the cache instead of refetching them".

Fix

query() keeps the entry it last read and falls back to it when the cache no longer holds that hash, building only when the hash is genuinely new. A removal now leaves the cache empty and fires no fetch, and the mounted reader holds its last value until options change or a real entry returns through setQueryData, a refetch or a later mount. cache.get() is consulted first on every read, so a live entry is never shadowed by the held-over one. The fallback is dropped on a client swap in syncClient, as it is only meaningful for the cache it was read from.

Because useInfiniteQuery and useQueries both go through this layer, the one change covers all three hooks.

Verification

The reporter's scenario, run against the react adapter on this branch to establish the reference, and against solid-query before and after the change:

react-query solid-query rc.1 solid-query, patched
cache events after removal removed removed, added, observerRemoved, observerAdded, ... removed
cache entries 0 1 0
getQueryData undefined 'v2' undefined
queryFn calls 1 2 1
mounted reader shows v1 v2 v1

The patched adapter matches react-query exactly, and matches 6.0.0-rc.0 as reported.

Two regression tests in useQuery-semantics.test.tsx. Both fail on the unpatched branch, the first on the event sequence and the second on the entry count:

  • leaves a removed query out of the cache while an observer is mounted, which asserts the cache event list is exactly ['removed'] with no 'added' behind it, an empty cache, an unchanged fetch count, and that the reader holds its last value rather than suspending over a key that no longer exists.
  • picks up a real entry written after a removal, which guards the fallback against becoming a stale read: a setQueryData after the removal rebuilds the entry, and the reader must track the new instance.

Full @tanstack/solid-query suite passes, 26 files and 348 tests, along with @tanstack/solid-query-devtools and @tanstack/solid-query-persist-client. test:eslint and tsc --build are clean.

The read layer bumps its per-hook version signal on every cache event for
its hash, 'removed' included, and the recompute that followed called
`queryCache.build()`, putting the entry the caller had just deleted
straight back. The resurrection was not passive: the rebuilt entry also
re-pointed the still-live observer, whose mount-fetch policy then
refetched and repopulated the key, so `removeQueries()` and `clear()`
could not be made to stick while any hook observed the key.

`query()` now reuses the entry it last read when the cache no longer
holds that hash, and builds only when the hash is genuinely new. A
removal leaves the cache empty and fires no fetch, while the mounted
reader holds its last value until options change or a real entry returns
through `setQueryData`, a refetch or a later mount. The resulting cache
event sequence, entry count, data and fetch count are identical to
react-query's for the same scenario, and to solid-query's at 6.0.0-rc.0.

Fixes TanStack#11350
@coderabbitai

coderabbitai Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

鈿欙笍 Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Team

Run ID: 0e7fc3d6-cf43-40cd-a402-adc0d5c8d7c1

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 馃攳 Trigger review

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.

1 participant