Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add always option to refetch options #1011

Merged
merged 1 commit into from
Sep 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 16 additions & 8 deletions docs/src/pages/docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down Expand Up @@ -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`.
Expand Down
20 changes: 13 additions & 7 deletions src/core/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,13 +234,19 @@ export class Query<TResult, TError> {
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()
Expand Down
5 changes: 4 additions & 1 deletion src/core/queryObserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ export class QueryObserver<TResult, TError> {
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()
Expand Down
16 changes: 11 additions & 5 deletions src/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
165 changes: 165 additions & 0 deletions src/react/tests/useQuery.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<number>[] = []
let count = 0

function Page() {
const state = useQuery(key, () => count++, {
staleTime: 0,
refetchOnWindowFocus: false,
})
states.push(state)
return null
}

render(<Page />)

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<number>[] = []
let count = 0

function Page() {
const state = useQuery(key, () => count++, {
staleTime: Infinity,
refetchOnWindowFocus: true,
})
states.push(state)
return null
}

render(<Page />)

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<number>[] = []
let count = 0

function Page() {
const state = useQuery(key, () => count++, {
staleTime: Infinity,
refetchOnWindowFocus: 'always',
})
states.push(state)
return null
}

render(<Page />)

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<string>[] = []

await queryCache.prefetchQuery(key, () => 'prefetched')

function Page() {
const state = useQuery(key, () => 'data', {
refetchOnMount: 'always',
staleTime: Infinity,
})
states.push(state)
return null
}

render(<Page />)

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<string>[] = []

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(<Page />)

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()
Expand Down