From ed996241339b1c7bd7597563190f68ea97905c7e Mon Sep 17 00:00:00 2001 From: Niek Date: Mon, 14 Sep 2020 17:47:27 +0200 Subject: [PATCH] feat: add always option to refetch options --- docs/src/pages/docs/api.md | 24 +++-- src/core/query.ts | 20 ++-- src/core/queryObserver.ts | 5 +- src/core/types.ts | 16 ++- src/react/tests/useQuery.test.tsx | 165 ++++++++++++++++++++++++++++++ 5 files changed, 209 insertions(+), 21 deletions(-) diff --git a/docs/src/pages/docs/api.md b/docs/src/pages/docs/api.md index 32babb187e..2efec1dfe6 100644 --- a/docs/src/pages/docs/api.md +++ b/docs/src/pages/docs/api.md @@ -94,12 +94,24 @@ const queryInfo = useQuery({ - `refetchIntervalInBackground: Boolean` - Optional - If set to `true`, queries that are set to continuously refetch with a `refetchInterval` will continue to refetch while their tab/window is in the background -- `refetchOnWindowFocus: Boolean` +- `refetchOnMount: boolean | "always"` - Optional - - Set this to `true` or `false` to enable/disable automatic refetching on window focus for this query. -- `refetchOnReconnect: Boolean` + - Defaults to `true` + - If set to `true`, the query will refetch on mount if the data is stale. + - If set to `false`, will disable additional instances of a query to trigger background refetches. + - If set to `"always"`, the query will always refetch on mount. +- `refetchOnWindowFocus: boolean | "always"` + - Optional + - Defaults to `true` + - If set to `true`, the query will refetch on window focus if the data is stale. + - If set to `false`, the query will not refetch on window focus. + - If set to `"always"`, the query will always refetch on window focus. +- `refetchOnReconnect: boolean | "always"` - Optional - - Set this to `true` or `false` to enable/disable automatic refetching on reconnect for this query. + - Defaults to `true` + - If set to `true`, the query will refetch on reconnect if the data is stale. + - If set to `false`, the query will not refetch on reconnect. + - If set to `"always"`, the query will always refetch on reconnect. - `notifyOnStatusChange: Boolean` - Optional - Set this to `false` to only re-render when there are changes to `data` or `error`. @@ -134,10 +146,6 @@ const queryInfo = useQuery({ - Optional - Defaults to `false` - Set this to `true` to always fetch when the component mounts (regardless of staleness). -- `refetchOnMount: Boolean` - - Optional - - Defaults to `true` - - If set to `false`, will disable additional instances of a query to trigger background refetches - `queryFnParamsFilter: Function(args) => filteredArgs` - Optional - This function will filter the params that get passed to `queryFn`. diff --git a/src/core/query.ts b/src/core/query.ts index ea4ed582db..b8d50fa309 100644 --- a/src/core/query.ts +++ b/src/core/query.ts @@ -234,13 +234,19 @@ export class Query { onInteraction(type: 'focus' | 'online'): void { // Execute the first observer which is enabled, // stale and wants to refetch on this interaction. - const staleObserver = this.observers.find( - observer => - observer.getCurrentResult().isStale && - observer.config.enabled && - ((observer.config.refetchOnWindowFocus && type === 'focus') || - (observer.config.refetchOnReconnect && type === 'online')) - ) + const staleObserver = this.observers.find(observer => { + const { config } = observer + const { isStale } = observer.getCurrentResult() + return ( + config.enabled && + ((type === 'focus' && + (config.refetchOnWindowFocus === 'always' || + (config.refetchOnWindowFocus && isStale))) || + (type === 'online' && + (config.refetchOnReconnect === 'always' || + (config.refetchOnReconnect && isStale)))) + ) + }) if (staleObserver) { staleObserver.fetch() diff --git a/src/core/queryObserver.ts b/src/core/queryObserver.ts index d46c2a4ae9..cbe1366f96 100644 --- a/src/core/queryObserver.ts +++ b/src/core/queryObserver.ts @@ -42,7 +42,10 @@ export class QueryObserver { this.listener = listener this.currentQuery.subscribeObserver(this) - if (this.config.enabled && this.config.forceFetchOnMount) { + if ( + this.config.enabled && + (this.config.forceFetchOnMount || this.config.refetchOnMount === 'always') + ) { this.fetch() } else { this.optionalFetch() diff --git a/src/core/types.ts b/src/core/types.ts index bc30aa9eca..0f3f636d05 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -95,20 +95,26 @@ export interface QueryObserverConfig< */ refetchIntervalInBackground?: boolean /** - * Set this to `true` or `false` to enable/disable automatic refetching on window focus for this query. + * If set to `true`, the query will refetch on window focus if the data is stale. + * If set to `false`, the query will not refetch on window focus. + * If set to `'always'`, the query will always refetch on window focus. * Defaults to `true`. */ - refetchOnWindowFocus?: boolean + refetchOnWindowFocus?: boolean | 'always' /** - * Set this to `true` or `false` to enable/disable automatic refetching on reconnect for this query. + * If set to `true`, the query will refetch on reconnect if the data is stale. + * If set to `false`, the query will not refetch on reconnect. + * If set to `'always'`, the query will always refetch on reconnect. * Defaults to `true`. */ - refetchOnReconnect?: boolean + refetchOnReconnect?: boolean | 'always' /** + * If set to `true`, the query will refetch on mount if the data is stale. * If set to `false`, will disable additional instances of a query to trigger background refetches. + * If set to `'always'`, the query will always refetch on mount. * Defaults to `true`. */ - refetchOnMount?: boolean + refetchOnMount?: boolean | 'always' /** * Set this to `true` to always fetch when the component mounts (regardless of staleness). * Defaults to `false`. diff --git a/src/react/tests/useQuery.test.tsx b/src/react/tests/useQuery.test.tsx index 3238399621..8420627a8d 100644 --- a/src/react/tests/useQuery.test.tsx +++ b/src/react/tests/useQuery.test.tsx @@ -1129,6 +1129,171 @@ describe('useQuery', () => { expect(queryFn).not.toHaveBeenCalled() }) + it('should not refetch stale query on focus when `refetchOnWindowFocus` is set to `false`', async () => { + const key = queryKey() + const states: QueryResult[] = [] + let count = 0 + + function Page() { + const state = useQuery(key, () => count++, { + staleTime: 0, + refetchOnWindowFocus: false, + }) + states.push(state) + return null + } + + render() + + await waitForMs(10) + + act(() => { + window.dispatchEvent(new FocusEvent('focus')) + }) + + await waitForMs(10) + + expect(states.length).toBe(2) + expect(states[0]).toMatchObject({ data: undefined, isFetching: true }) + expect(states[1]).toMatchObject({ data: 0, isFetching: false }) + }) + + it('should not refetch fresh query on focus when `refetchOnWindowFocus` is set to `true`', async () => { + const key = queryKey() + const states: QueryResult[] = [] + let count = 0 + + function Page() { + const state = useQuery(key, () => count++, { + staleTime: Infinity, + refetchOnWindowFocus: true, + }) + states.push(state) + return null + } + + render() + + await waitForMs(10) + + act(() => { + window.dispatchEvent(new FocusEvent('focus')) + }) + + await waitForMs(10) + + expect(states.length).toBe(2) + expect(states[0]).toMatchObject({ data: undefined, isFetching: true }) + expect(states[1]).toMatchObject({ data: 0, isFetching: false }) + }) + + it('should refetch fresh query on focus when `refetchOnWindowFocus` is set to `always`', async () => { + const key = queryKey() + const states: QueryResult[] = [] + let count = 0 + + function Page() { + const state = useQuery(key, () => count++, { + staleTime: Infinity, + refetchOnWindowFocus: 'always', + }) + states.push(state) + return null + } + + render() + + await waitForMs(10) + + act(() => { + window.dispatchEvent(new FocusEvent('focus')) + }) + + await waitForMs(10) + + expect(states.length).toBe(4) + expect(states[0]).toMatchObject({ data: undefined, isFetching: true }) + expect(states[1]).toMatchObject({ data: 0, isFetching: false }) + expect(states[2]).toMatchObject({ data: 0, isFetching: true }) + expect(states[3]).toMatchObject({ data: 1, isFetching: false }) + }) + + it('should refetch fresh query when refetchOnMount is set to always', async () => { + const key = queryKey() + const states: QueryResult[] = [] + + await queryCache.prefetchQuery(key, () => 'prefetched') + + function Page() { + const state = useQuery(key, () => 'data', { + refetchOnMount: 'always', + staleTime: Infinity, + }) + states.push(state) + return null + } + + render() + + await waitForMs(10) + + expect(states.length).toBe(3) + expect(states[0]).toMatchObject({ + data: 'prefetched', + isStale: false, + isFetching: false, + }) + expect(states[1]).toMatchObject({ + data: 'prefetched', + isStale: false, + isFetching: true, + }) + expect(states[2]).toMatchObject({ + data: 'data', + isStale: false, + isFetching: false, + }) + }) + + it('should refetch stale query when refetchOnMount is set to true', async () => { + const key = queryKey() + const states: QueryResult[] = [] + + await queryCache.prefetchQuery(key, () => 'prefetched') + + await sleep(10) + + function Page() { + const state = useQuery(key, () => 'data', { + refetchOnMount: true, + staleTime: 0, + }) + states.push(state) + return null + } + + render() + + await waitForMs(10) + + expect(states.length).toBe(3) + expect(states[0]).toMatchObject({ + data: 'prefetched', + isStale: true, + isFetching: false, + }) + expect(states[1]).toMatchObject({ + data: 'prefetched', + isStale: true, + isFetching: true, + }) + expect(states[2]).toMatchObject({ + data: 'data', + isStale: true, + isFetching: false, + }) + }) + it('should set status to error if queryFn throws', async () => { const key = queryKey() const consoleMock = mockConsoleError()