Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/reset-static-queries.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/query-core': patch
---

fix(query-core): only skip static queries in `refetchQueries` when they already have data, so `resetQueries`, `invalidateQueries` and `refetchQueries` refetch an active static query that has no data (for example after a reset or a failed first fetch)
41 changes: 41 additions & 0 deletions packages/query-core/src/__tests__/queryClient.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2558,6 +2558,28 @@ describe('queryClient', () => {

unsubscribe()
})

it('should refetch static queries without data after invalidation', async () => {
const key = queryKey()
const queryFn = vi
.fn<(...args: Array<unknown>) => Promise<string>>()
.mockRejectedValueOnce(new Error('error'))
.mockResolvedValue('data')
const observer = new QueryObserver(queryClient, {
queryKey: key,
queryFn,
staleTime: 'static',
retry: false,
})
const unsubscribe = observer.subscribe(() => undefined)
await vi.advanceTimersByTimeAsync(0)
expect(queryFn).toHaveBeenCalledTimes(1)
expect(queryClient.getQueryState(key)?.status).toBe('error')
await queryClient.invalidateQueries({ queryKey: key })
expect(queryFn).toHaveBeenCalledTimes(2)
expect(queryClient.getQueryData(key)).toBe('data')
unsubscribe()
})
})

describe('resetQueries', () => {
Expand Down Expand Up @@ -2694,6 +2716,25 @@ describe('queryClient', () => {
expect(queryFn2).toHaveBeenCalledTimes(0)
expect(didSkipTokenRun).toBe(false)
})

it('should refetch active queries with a static staleTime', async () => {
const key = queryKey()
const queryFn = vi
.fn<(...args: Array<unknown>) => string>()
.mockReturnValue('data')
await queryClient.query({ queryKey: key, queryFn })
const observer = new QueryObserver(queryClient, {
queryKey: key,
queryFn,
staleTime: 'static',
})
const unsubscribe = observer.subscribe(() => undefined)
expect(queryFn).toHaveBeenCalledTimes(1)
await queryClient.resetQueries({ queryKey: key })
expect(queryFn).toHaveBeenCalledTimes(2)
expect(queryClient.getQueryData(key)).toBe('data')
unsubscribe()
})
})

describe('focusManager and onlineManager', () => {
Expand Down
11 changes: 8 additions & 3 deletions packages/query-core/src/queryClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -515,8 +515,9 @@ export class QueryClient {

/**
* Refetches queries matching the given filters, regardless of whether they are stale. Without
* filters, every query in the cache is refetched. Queries that are disabled, or static (only
* have observers with a static `staleTime`), are never refetched.
* filters, every query in the cache is refetched. Disabled queries are never refetched, and
* static queries (with an observer whose `staleTime` is `'static'`) are skipped when they
* already have data.
*
* By default (`cancelRefetch: true`), a currently running fetch is cancelled before the new
* one starts. The returned promise resolves once all matching queries have settled; it does
Expand All @@ -543,7 +544,11 @@ export class QueryClient {
const promises = notifyManager.batch(() =>
this.#queryCache
.findAll(filters)
.filter((query) => !query.isDisabled() && !query.isStatic())
.filter(
(query) =>
!query.isDisabled() &&
!(query.isStatic() && query.state.data !== undefined),
)
.map((query) => {
let promise = query.fetch(undefined, fetchOptions)
if (!fetchOptions.throwOnError) {
Expand Down