Skip to content

Commit 4d23e82

Browse files
fix(solid-query): don't trigger Suspense when data is already cached
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 #9955
1 parent a37c003 commit 4d23e82

3 files changed

Lines changed: 55 additions & 0 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@tanstack/solid-query': patch
3+
---
4+
5+
fix(solid-query): avoid triggering Suspense when data is already cached (e.g. via `ensureQueryData`)

packages/solid-query/src/__tests__/suspense.test.tsx

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -908,6 +908,47 @@ describe("useQuery's in Suspense mode", () => {
908908
consoleMock.mockRestore()
909909
})
910910

911+
// https://github.com/TanStack/query/issues/9955
912+
it('should not trigger Suspense when data was preloaded via ensureQueryData', async () => {
913+
const key = queryKey()
914+
915+
const ensurePromise = queryClient.ensureQueryData({
916+
queryKey: key,
917+
queryFn: () => sleep(10).then(() => 'preloaded'),
918+
staleTime: Infinity,
919+
})
920+
await vi.advanceTimersByTimeAsync(10)
921+
await ensurePromise
922+
923+
let fallbackMounted = false
924+
925+
function Page() {
926+
const state = useQuery(() => ({
927+
queryKey: key,
928+
queryFn: () => sleep(10).then(() => 'fresh'),
929+
staleTime: Infinity,
930+
}))
931+
932+
return <div>data: {state.data}</div>
933+
}
934+
935+
function Fallback() {
936+
fallbackMounted = true
937+
return <>loading</>
938+
}
939+
940+
const rendered = render(() => (
941+
<QueryClientProvider client={queryClient}>
942+
<Suspense fallback={<Fallback />}>
943+
<Page />
944+
</Suspense>
945+
</QueryClientProvider>
946+
))
947+
948+
expect(rendered.getByText('data: preloaded')).toBeInTheDocument()
949+
expect(fallbackMounted).toBe(false)
950+
})
951+
911952
it('should render the correct amount of times in Suspense mode when gcTime is set to 0', async () => {
912953
const key = queryKey()
913954
let state: UseQueryResult<number> | null = null

packages/solid-query/src/useBaseQuery.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
createSignal,
1111
on,
1212
onCleanup,
13+
untrack,
1314
} from 'solid-js'
1415
import { createStore, reconcile, unwrap } from 'solid-js/store'
1516
import { useQueryClient } from './QueryClientProvider'
@@ -377,6 +378,14 @@ export function useBaseQuery<
377378
): any {
378379
if (prop === 'data') {
379380
if (state.data !== undefined) {
381+
// When data is already in the store and no fetch is in-flight (e.g.
382+
// it was preloaded via `ensureQueryData`), avoid reading the resource
383+
// because its initial pending state would otherwise trigger Suspense
384+
// on the synchronous-resolve microtask gap. See #9955.
385+
// `untrack` keeps `isFetching` from leaking into the data subscriber.
386+
if (!untrack(() => state.isFetching)) {
387+
return state.data
388+
}
380389
return queryResource.latest?.data
381390
}
382391
return queryResource()?.data

0 commit comments

Comments
 (0)