diff --git a/.changeset/quiet-options-refetch.md b/.changeset/quiet-options-refetch.md new file mode 100644 index 0000000000..736d8cc90e --- /dev/null +++ b/.changeset/quiet-options-refetch.md @@ -0,0 +1,5 @@ +--- +'@tanstack/svelte-query': patch +--- + +Prevent reactive option updates from cancelling an in-flight refetch when the query identity and enabled state are unchanged. \ No newline at end of file diff --git a/packages/svelte-query/src/createBaseQuery.svelte.ts b/packages/svelte-query/src/createBaseQuery.svelte.ts index 03fc6b28db..3da83d0fe7 100644 --- a/packages/svelte-query/src/createBaseQuery.svelte.ts +++ b/packages/svelte-query/src/createBaseQuery.svelte.ts @@ -1,3 +1,4 @@ +import { untrack } from 'svelte' import { useIsRestoring } from './useIsRestoring.js' import { useQueryClient } from './useQueryClient.js' import { createRawRef } from './containers.svelte.js' @@ -74,7 +75,7 @@ export function createBaseQuery< $effect(() => { const unsubscribe = isRestoring.current ? () => undefined - : observer.subscribe(() => update(createResult())) + : observer.subscribe(() => untrack(() => update(createResult()))) observer.updateResult() return unsubscribe }) diff --git a/packages/svelte-query/tests/createInfiniteQuery/createInfiniteQuery.svelte.test.ts b/packages/svelte-query/tests/createInfiniteQuery/createInfiniteQuery.svelte.test.ts index 4ecf75b351..9a2d7c2d96 100644 --- a/packages/svelte-query/tests/createInfiniteQuery/createInfiniteQuery.svelte.test.ts +++ b/packages/svelte-query/tests/createInfiniteQuery/createInfiniteQuery.svelte.test.ts @@ -1,7 +1,14 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { fireEvent, render } from '@testing-library/svelte' +import { flushSync } from 'svelte' import { QueryClient } from '@tanstack/query-core' -import { ref } from '../utils.svelte.js' +import { + InfiniteQueryObserver, + createInfiniteQuery, + focusManager, + infiniteQueryOptions, +} from '../../src/index.js' +import { promiseWithResolvers, ref } from '../utils.svelte.js' import Base from './Base.svelte' import Select from './Select.svelte' import ChangeClient from './ChangeClient.svelte' @@ -171,4 +178,97 @@ describe('createInfiniteQuery', () => { rendered.getByText('Data: {"pages":[7,8],"pageParams":[7,8]}'), ).toBeInTheDocument() }) + + it('does not cancel a refetch when reactive options retain the same values', async () => { + let state = $state({ enabled: true }) + let now = Date.now() + vi.spyOn(Date, 'now').mockImplementation(() => now) + + queryClient = new QueryClient({ + defaultOptions: { + queries: { staleTime: 60_000, retry: false }, + }, + }) + queryClient.mount() + + const key = ['items'] as const + let response: ReturnType> | undefined + const reads: Array<{ page: number; signal: AbortSignal }> = [] + + const options = () => + infiniteQueryOptions({ + queryKey: key, + enabled: state.enabled, + initialPageParam: 0, + queryFn: async ({ pageParam, signal }) => { + reads.push({ page: pageParam, signal }) + await response?.promise + return { + items: [pageParam], + next: pageParam === 0 ? 1 : undefined, + } + }, + getNextPageParam: (page) => page.next, + }) + + const first = new InfiniteQueryObserver(queryClient, options()) + const stop = first.subscribe(() => {}) + await vi.waitFor(() => + expect(first.getCurrentResult().isSuccess).toBe(true), + ) + await first.fetchNextPage() + stop() + + now += 60_001 + const dispose = $effect.root(() => { + const query = createInfiniteQuery(options, () => queryClient) + $effect(() => { + void query.data + void query.isFetchingNextPage + void query.fetchStatus + }) + }) + + try { + flushSync() + await vi.waitFor(() => expect(reads).toHaveLength(4)) + await vi.waitFor(() => + expect(queryClient.getQueryState(key)?.fetchStatus).toBe('idle'), + ) + flushSync() + + now += 60_001 + reads.length = 0 + response = promiseWithResolvers() + + focusManager.setFocused(false) + focusManager.setFocused(true) + await vi.waitFor(() => expect(reads).toHaveLength(1)) + + state = { enabled: true } + flushSync() + + const aborted = reads[0]!.signal.aborted + response.resolve() + + await vi.waitFor(() => + expect(queryClient.getQueryState(key)?.fetchStatus).toBe('idle'), + ) + + expect({ + aborted, + pages: reads.map(({ page }) => page), + }).toEqual({ + aborted: false, + pages: [0, 1], + }) + } finally { + response?.resolve() + dispose() + queryClient.clear() + queryClient.unmount() + vi.restoreAllMocks() + focusManager.setFocused(true) + } + }) })