From df530d6b87827b8609d568dd040e921de8e22a5a Mon Sep 17 00:00:00 2001 From: Aaron Jensen Date: Sun, 13 Dec 2020 12:35:15 -0600 Subject: [PATCH] feat: resetQueries refetches active queries (#1397) --- docs/src/pages/reference/QueryClient.md | 7 +- src/core/queryClient.ts | 18 +++- src/core/tests/queryCache.test.tsx | 27 +++++ src/core/types.ts | 4 + src/react/tests/useQuery.test.tsx | 125 ++++++++++++++++++++++++ 5 files changed, 174 insertions(+), 7 deletions(-) diff --git a/docs/src/pages/reference/QueryClient.md b/docs/src/pages/reference/QueryClient.md index 4e34813aee..f3e0694e60 100644 --- a/docs/src/pages/reference/QueryClient.md +++ b/docs/src/pages/reference/QueryClient.md @@ -284,7 +284,7 @@ property/state of the query. This will notify subscribers — unlike `clear`, which removes all subscribers — and reset the query to its pre-loaded state — unlike `invalidateQueries`. If a query has `initialData`, the query's data will be -reset to that. +reset to that. If a query is active, it will be refetched. ```js queryClient.resetQueries(queryKey, { exact: true }) @@ -294,10 +294,13 @@ queryClient.resetQueries(queryKey, { exact: true }) - `queryKey?: QueryKey`: [Query Keys](../guides/query-keys) - `filters?: QueryFilters`: [Query Filters](../guides/query-filters) +- `resetOptions?: ResetOptions`: + - `throwOnError?: boolean` + - When set to `true`, this method will throw if any of the query refetch tasks fail. **Returns** -This method does not return anything +This method returns a promise that resolves when all active queries have been refetched. ## `queryClient.isFetching` diff --git a/src/core/queryClient.ts b/src/core/queryClient.ts index af2a300744..44b4904909 100644 --- a/src/core/queryClient.ts +++ b/src/core/queryClient.ts @@ -20,6 +20,7 @@ import type { QueryObserverOptions, QueryOptions, RefetchOptions, + ResetOptions, } from './types' import type { QueryState, SetDataOptions } from './query' import { QueryCache } from './queryCache' @@ -132,15 +133,22 @@ export class QueryClient { }) } - resetQueries(filters?: QueryFilters): void - resetQueries(queryKey?: QueryKey, filters?: QueryFilters): void - resetQueries(arg1?: QueryKey | QueryFilters, arg2?: QueryFilters): void { - const [filters] = parseFilterArgs(arg1, arg2) + resetQueries(filters?: QueryFilters, options?: ResetOptions): Promise + resetQueries(queryKey?: QueryKey, filters?: QueryFilters, options?: ResetOptions): Promise + resetQueries(arg1?: QueryKey | QueryFilters, arg2?: QueryFilters | ResetOptions, arg3?: ResetOptions): Promise { + const [filters, options] = parseFilterArgs(arg1, arg2, arg3) const queryCache = this.queryCache - notifyManager.batch(() => { + + const refetchFilters: QueryFilters = { + ...filters, + active: true, + } + + return notifyManager.batch(() => { queryCache.findAll(filters).forEach(query => { query.reset() }) + return this.refetchQueries(refetchFilters, options) }) } diff --git a/src/core/tests/queryCache.test.tsx b/src/core/tests/queryCache.test.tsx index 1470d7d7e4..8fa80a6fd4 100644 --- a/src/core/tests/queryCache.test.tsx +++ b/src/core/tests/queryCache.test.tsx @@ -998,6 +998,33 @@ describe('queryCache', () => { expect(state?.data).toEqual('initial') }) + test('resetQueries should refetch all active queries', async () => { + const key1 = queryKey() + const key2 = queryKey() + const queryFn1 = jest.fn() + const queryFn2 = jest.fn() + const testCache = new QueryCache() + const testClient = new QueryClient({ queryCache: testCache }) + const observer1 = new QueryObserver(testClient, { + queryKey: key1, + queryFn: queryFn1, + enabled: true, + }) + const observer2 = new QueryObserver(testClient, { + queryKey: key2, + queryFn: queryFn2, + enabled: false, + }) + observer1.subscribe() + observer2.subscribe() + await testClient.resetQueries() + observer2.destroy() + observer1.destroy() + testCache.clear() + expect(queryFn1).toHaveBeenCalledTimes(2) + expect(queryFn2).toHaveBeenCalledTimes(0) + }) + test('find should filter correctly', async () => { const key = queryKey() const testCache = new QueryCache() diff --git a/src/core/types.ts b/src/core/types.ts index 2bfe1e27a8..1a785cb4cd 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -214,6 +214,10 @@ export interface InvalidateOptions { throwOnError?: boolean } +export interface ResetOptions { + throwOnError?: boolean +} + export interface FetchNextPageOptions extends ResultOptions { pageParam?: unknown } diff --git a/src/react/tests/useQuery.test.tsx b/src/react/tests/useQuery.test.tsx index 6cfbf29168..6f73279e67 100644 --- a/src/react/tests/useQuery.test.tsx +++ b/src/react/tests/useQuery.test.tsx @@ -2852,4 +2852,129 @@ describe('useQuery', () => { expect(cancelFn).toHaveBeenCalled() }) + + it('should update query state and refetch when reset with resetQueries', async () => { + const key = queryKey() + const states: UseQueryResult[] = [] + let count = 0 + + function Page() { + const state = useQuery( + key, + () => { + count++ + return count + }, + { staleTime: Infinity } + ) + + states.push(state) + + React.useEffect(() => { + setActTimeout(() => { + queryClient.resetQueries(key) + }, 10) + }, []) + + return null + } + + renderWithClient(queryClient, ) + + await sleep(100) + + expect(states.length).toBe(4) + expect(states[0]).toMatchObject({ + data: undefined, + isLoading: true, + isFetching: true, + isSuccess: false, + isStale: true, + }) + expect(states[1]).toMatchObject({ + data: 1, + isLoading: false, + isFetching: false, + isSuccess: true, + isStale: false, + }) + expect(states[2]).toMatchObject({ + data: undefined, + isLoading: true, + isFetching: true, + isSuccess: false, + isStale: true, + }) + expect(states[3]).toMatchObject({ + data: 2, + isLoading: false, + isFetching: false, + isSuccess: true, + isStale: false, + }) + }) + + it('should update query state and not refetch when resetting a disabled query with resetQueries', async () => { + const key = queryKey() + const states: UseQueryResult[] = [] + let count = 0 + + function Page() { + const state = useQuery( + key, + () => { + count++ + return count + }, + { staleTime: Infinity, enabled: false } + ) + + states.push(state) + + React.useEffect(() => { + setActTimeout(() => { + state.refetch() + }, 0) + setActTimeout(() => { + queryClient.resetQueries(key) + }, 50) + }, []) + + return null + } + + renderWithClient(queryClient, ) + + await sleep(100) + + expect(states.length).toBe(4) + expect(states[0]).toMatchObject({ + data: undefined, + isLoading: false, + isFetching: false, + isSuccess: false, + isStale: true, + }) + expect(states[1]).toMatchObject({ + data: undefined, + isLoading: true, + isFetching: true, + isSuccess: false, + isStale: true, + }) + expect(states[2]).toMatchObject({ + data: 1, + isLoading: false, + isFetching: false, + isSuccess: true, + isStale: false, + }) + expect(states[3]).toMatchObject({ + data: undefined, + isLoading: false, + isFetching: false, + isSuccess: false, + isStale: true, + }) + }) })