diff --git a/.changeset/solid-query-suspense-ensure-data.md b/.changeset/solid-query-suspense-ensure-data.md new file mode 100644 index 0000000000..d1d72d2701 --- /dev/null +++ b/.changeset/solid-query-suspense-ensure-data.md @@ -0,0 +1,5 @@ +--- +'@tanstack/solid-query': patch +--- + +fix(solid-query): avoid triggering Suspense when data is already cached (e.g. via `ensureQueryData`) diff --git a/packages/solid-query/src/__tests__/suspense.test.tsx b/packages/solid-query/src/__tests__/suspense.test.tsx index b3aa93e4b7..21199286d8 100644 --- a/packages/solid-query/src/__tests__/suspense.test.tsx +++ b/packages/solid-query/src/__tests__/suspense.test.tsx @@ -851,6 +851,49 @@ describe("useQuery's in Suspense mode", () => { consoleMock.mockRestore() }) + // https://github.com/TanStack/query/issues/9955 + it('should not trigger Suspense when data was preloaded via ensureQueryData', async () => { + const key = queryKey() + + const ensurePromise = queryClient.ensureQueryData({ + queryKey: key, + queryFn: () => sleep(10).then(() => 'preloaded'), + staleTime: Infinity, + }) + await vi.advanceTimersByTimeAsync(10) + await ensurePromise + + let fallbackMounted = false + const pageQueryFn = vi.fn(() => sleep(10).then(() => 'fresh')) + + function Page() { + const state = useQuery(() => ({ + queryKey: key, + queryFn: pageQueryFn, + staleTime: Infinity, + })) + + return
data: {state.data}
+ } + + function Fallback() { + fallbackMounted = true + return <>loading + } + + const rendered = render(() => ( + + }> + + + + )) + + expect(rendered.getByText('data: preloaded')).toBeInTheDocument() + expect(fallbackMounted).toBe(false) + expect(pageQueryFn).not.toHaveBeenCalled() + }) + it('should render the correct amount of times in Suspense mode when gcTime is set to 0', async () => { const key = queryKey() let state: UseQueryResult | null = null diff --git a/packages/solid-query/src/useBaseQuery.ts b/packages/solid-query/src/useBaseQuery.ts index 1cc45a420b..e8764bb452 100644 --- a/packages/solid-query/src/useBaseQuery.ts +++ b/packages/solid-query/src/useBaseQuery.ts @@ -10,6 +10,7 @@ import { createSignal, on, onCleanup, + untrack, } from 'solid-js' import { createStore, reconcile, unwrap } from 'solid-js/store' import { useQueryClientResolver } from './QueryClientProvider' @@ -375,6 +376,14 @@ export function useBaseQuery< ): any { if (prop === 'data') { if (state.data !== undefined) { + // When data is already in the store and no fetch is in-flight (e.g. + // it was preloaded via `ensureQueryData`), avoid reading the resource + // because its initial pending state would otherwise trigger Suspense + // on the synchronous-resolve microtask gap. See #9955. + // `untrack` keeps `isFetching` from leaking into the data subscriber. + if (!untrack(() => state.isFetching)) { + return state.data + } return queryResource.latest?.data } return queryResource()?.data