Skip to content

fix(solid-query): don't trigger Suspense when data is already cached#10592

Open
ousamabenyounes wants to merge 2 commits intoTanStack:mainfrom
ousamabenyounes:fix/issue-9955
Open

fix(solid-query): don't trigger Suspense when data is already cached#10592
ousamabenyounes wants to merge 2 commits intoTanStack:mainfrom
ousamabenyounes:fix/issue-9955

Conversation

@ousamabenyounes
Copy link
Copy Markdown
Contributor

@ousamabenyounes ousamabenyounes commented Apr 26, 2026

Summary

Fixes #9955.

When data was preloaded via ensureQueryData (e.g. from a TanStack Router loader), accessing state.data in a Suspense boundary would re-trigger the Suspense fallback even though the data was already in cache and isSuccess was true.

Root cause

In useBaseQuery.ts, the proxy data getter reads queryResource.latest?.data whenever state.data !== undefined. Per Solid's docs, .latest falls back to a read()-equivalent (and therefore triggers Suspense) when the resource has no prior resolved value.

In the preloaded-cache case, the resource's fetcher synchronously calls resolve(...) because !observerResult.isLoading, but the resource doesn't transition out of the initial pending state until the next microtask. Reading .latest inside that microtask gap suspends the component even though state.data already holds the cached value.

Fix

If the store already has data and no fetch is in-flight (!state.isFetching), return state.data directly without going through queryResource. state.isFetching is read via untrack so it doesn't widen the data subscriber's reactive deps (preserves existing reconcile / memo-churn tests).

When a fetch is in-flight, the existing queryResource.latest?.data path is preserved so background-refresh and re-mount semantics are unchanged.

Verification

  • Added regression test in packages/solid-query/src/__tests__/suspense.test.tsx that pre-populates the cache via queryClient.ensureQueryData(...), renders inside <Suspense>, and asserts the fallback never mounts. Test fails on main (Suspense fires), passes with the fix.
  • pnpm exec nx run @tanstack/solid-query:test:lib — all 310 tests pass.
  • pnpm exec nx run @tanstack/solid-query:test:types — all TS versions pass.
  • pnpm exec nx run @tanstack/solid-query:test:eslint — clean.
  • Changeset added (patch for @tanstack/solid-query).

Test plan

🤖 Generated with Claude Code
Generated by Ora Studio
Vibe coded by ousamabenyounes

Summary by CodeRabbit

  • Bug Fixes

    • Prevented unnecessary Suspense activation when query data is already available (including data preloaded into cache), so components render immediately without showing loading fallbacks.
  • Tests

    • Added regression tests to ensure Suspense fallbacks do not mount when data is cached or preloaded.
  • Chores

    • Added a changeset patch note documenting the fix.

Review Change Stack


Generated by Ora Studio (with Claude Code)
Vibe coded by Ben Younes Ousama

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 26, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR prevents unnecessary Solid Suspense activation when query data is already cached (including ensureQueryData preloads) by changing the data getter to return cached state.data only when no fetch is in-flight, adding a regression test, and adding a changeset note.

Changes

Solid Query Suspense Fix

Layer / File(s) Summary
Imports
packages/solid-query/src/useBaseQuery.ts
Adds untrack to Solid imports to allow conditional dependency checks.
Core Getter Change
packages/solid-query/src/useBaseQuery.ts
prop === 'data' now returns state.data only when untrack(() => state.isFetching) is falsy; otherwise it reads from the Solid resource (queryResource.latest?.data / queryResource()?.data).
Tests
packages/solid-query/src/__tests__/suspense.test.tsx
New test preloads data via queryClient.ensureQueryData (with staleTime: Infinity) and asserts that mounting useQuery inside Suspense renders the preloaded value without mounting the fallback.
Changeset Documentation
.changeset/solid-query-suspense-ensure-data.md
Patch release note describing the fix to avoid triggering Suspense when data is already available in cache, including ensureQueryData scenarios.

Sequence Diagram(s)

sequenceDiagram
  participant Component
  participant useBaseQuery
  participant QueryResource
  participant QueryClient
  Component->>useBaseQuery: read data
  useBaseQuery->>useBaseQuery: check state.data
  useBaseQuery->>useBaseQuery: untrack(state.isFetching)
  alt not fetching and state.data present
    useBaseQuery-->>Component: return cached state.data
  else fetching or no cached data
    useBaseQuery->>QueryResource: read resource (may suspend)
    QueryResource-->>useBaseQuery: resource.data
    useBaseQuery-->>Component: return resource.data (or suspend)
  end
  Note right of QueryClient: ensureQueryData preloads cache used above
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I peeked at state without a fright,
Preloaded data shining bright,
A little untrack kept Suspense away,
Components rendered right away,
Hooray — no fallback in sight ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main fix: preventing Suspense triggers when cached data is accessed after preloading via ensureQueryData.
Description check ✅ Passed The PR description comprehensively covers the bug, root cause analysis, the fix implementation, verification steps, and test coverage against the template requirements.
Linked Issues check ✅ Passed The PR fully addresses issue #9955 by fixing the Suspense trigger when accessing cached data after ensureQueryData, including regression test and verification.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing issue #9955: the proxy getter logic in useBaseQuery.ts, the regression test, and the changeset entry.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@nx-cloud
Copy link
Copy Markdown

nx-cloud Bot commented Apr 26, 2026

View your CI Pipeline Execution ↗ for commit 4e59da3

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

☁️ Nx Cloud last updated this comment at 2026-05-10 08:17:54 UTC

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 26, 2026

More templates

@tanstack/angular-query-experimental

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

@tanstack/eslint-plugin-query

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

@tanstack/lit-query

npm i https://pkg.pr.new/@tanstack/lit-query@10592

@tanstack/preact-query

npm i https://pkg.pr.new/@tanstack/preact-query@10592

@tanstack/preact-query-devtools

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

@tanstack/preact-query-persist-client

npm i https://pkg.pr.new/@tanstack/preact-query-persist-client@10592

@tanstack/query-async-storage-persister

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

@tanstack/query-broadcast-client-experimental

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

@tanstack/query-core

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

@tanstack/query-devtools

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

@tanstack/query-persist-client-core

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

@tanstack/query-sync-storage-persister

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

@tanstack/react-query

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

@tanstack/react-query-devtools

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

@tanstack/react-query-next-experimental

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

@tanstack/react-query-persist-client

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

@tanstack/solid-query

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

@tanstack/solid-query-devtools

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

@tanstack/solid-query-persist-client

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

@tanstack/svelte-query

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

@tanstack/svelte-query-devtools

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

@tanstack/svelte-query-persist-client

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

@tanstack/vue-query

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

@tanstack/vue-query-devtools

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

commit: 4e59da3

@ousamabenyounes ousamabenyounes force-pushed the fix/issue-9955 branch 2 times, most recently from c947290 to 7a99cf8 Compare May 8, 2026 08:18
Copy link
Copy Markdown
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.

🧹 Nitpick comments (1)
packages/solid-query/src/__tests__/suspense.test.tsx (1)

911-950: ⚡ Quick win

Consider spying on the Page query function to assert it was never invoked.

The test correctly guards against the fallback mounting, but it does not verify that the background queryFn (returning 'fresh') is never called. A vi.fn() spy would make the test more explicit about the "no refetch" contract implied by staleTime: Infinity, and would catch regressions where the fix avoids Suspense but still triggers an unintended network call.

✅ Suggested improvement
+    const freshFn = vi.fn(() => sleep(10).then(() => 'fresh'))
+
     function Page() {
       const state = useQuery(() => ({
         queryKey: key,
-        queryFn: () => sleep(10).then(() => 'fresh'),
+        queryFn: freshFn,
         staleTime: Infinity,
       }))

       return <div>data: {state.data}</div>
     }

Then add at the end:

     expect(rendered.getByText('data: preloaded')).toBeInTheDocument()
     expect(fallbackMounted).toBe(false)
+    expect(freshFn).not.toHaveBeenCalled()
   })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/solid-query/src/__tests__/suspense.test.tsx` around lines 911 - 950,
Add a vi.fn() spy for the queryFn used inside the Page component and assert it
was never called after render: replace the inline queryFn in Page's useQuery
with a spied function (e.g., const pageQueryFn = vi.fn(() => sleep(10).then(()
=> 'fresh')) and pass that to useQuery), then after rendering assert pageQueryFn
was not called (expect(pageQueryFn).not.toHaveBeenCalled()) to ensure the
background refetch never ran; keep the ensureQueryData preload untouched.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/solid-query/src/__tests__/suspense.test.tsx`:
- Around line 911-950: Add a vi.fn() spy for the queryFn used inside the Page
component and assert it was never called after render: replace the inline
queryFn in Page's useQuery with a spied function (e.g., const pageQueryFn =
vi.fn(() => sleep(10).then(() => 'fresh')) and pass that to useQuery), then
after rendering assert pageQueryFn was not called
(expect(pageQueryFn).not.toHaveBeenCalled()) to ensure the background refetch
never ran; keep the ensureQueryData preload untouched.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 39a9d48e-01a7-4062-9fd2-663ae1782d1f

📥 Commits

Reviewing files that changed from the base of the PR and between c947290 and 7a99cf8.

📒 Files selected for processing (3)
  • .changeset/solid-query-suspense-ensure-data.md
  • packages/solid-query/src/__tests__/suspense.test.tsx
  • packages/solid-query/src/useBaseQuery.ts
✅ Files skipped from review due to trivial changes (1)
  • .changeset/solid-query-suspense-ensure-data.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/solid-query/src/useBaseQuery.ts

ousamabenyounes added a commit to ousamabenyounes/query that referenced this pull request May 8, 2026
Address CodeRabbit nitpick on PR TanStack#10592: spy on Page's queryFn and
assert it was never called, strengthening the guarantee that the
background refetch did not run when data was preloaded via
ensureQueryData.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ousamabenyounes
Copy link
Copy Markdown
Contributor Author

Addressed CodeRabbit nitpick in e815276: spied on Page's queryFn with vi.fn() and added expect(pageQueryFn).not.toHaveBeenCalled() to assert the background refetch never ran.

ousamabenyounes added a commit to ousamabenyounes/query that referenced this pull request May 9, 2026
Address CodeRabbit nitpick on PR TanStack#10592: spy on Page's queryFn and
assert it was never called, strengthening the guarantee that the
background refetch did not run when data was preloaded via
ensureQueryData.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ousamabenyounes and others added 2 commits May 10, 2026 08:12
When data was preloaded via `ensureQueryData` (e.g. from a router loader),
the proxy `data` getter read `queryResource.latest`, which falls back to a
suspending read while the resource is in its initial pending state, even
though the fetcher had already synchronously resolved with cached data.

If the store already has data and no fetch is in-flight, return `state.data`
directly. `state.isFetching` is read via `untrack` so it doesn't widen the
data subscriber's reactive deps.

Fixes TanStack#9955
Address CodeRabbit nitpick on PR TanStack#10592: spy on Page's queryFn and
assert it was never called, strengthening the guarantee that the
background refetch did not run when data was preloaded via
ensureQueryData.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

Solid Suspense is triggered on data access after checking isSuccess when loaded by ensureQueryData

1 participant