Skip to content

Commit 959e19e

Browse files
authored
feat: add always option to refetch options (#1011)
1 parent 460ab33 commit 959e19e

File tree

5 files changed

+209
-21
lines changed

5 files changed

+209
-21
lines changed

docs/src/pages/docs/api.md

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,24 @@ const queryInfo = useQuery({
9494
- `refetchIntervalInBackground: Boolean`
9595
- Optional
9696
- 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
97-
- `refetchOnWindowFocus: Boolean`
97+
- `refetchOnMount: boolean | "always"`
9898
- Optional
99-
- Set this to `true` or `false` to enable/disable automatic refetching on window focus for this query.
100-
- `refetchOnReconnect: Boolean`
99+
- Defaults to `true`
100+
- If set to `true`, the query will refetch on mount if the data is stale.
101+
- If set to `false`, will disable additional instances of a query to trigger background refetches.
102+
- If set to `"always"`, the query will always refetch on mount.
103+
- `refetchOnWindowFocus: boolean | "always"`
104+
- Optional
105+
- Defaults to `true`
106+
- If set to `true`, the query will refetch on window focus if the data is stale.
107+
- If set to `false`, the query will not refetch on window focus.
108+
- If set to `"always"`, the query will always refetch on window focus.
109+
- `refetchOnReconnect: boolean | "always"`
101110
- Optional
102-
- Set this to `true` or `false` to enable/disable automatic refetching on reconnect for this query.
111+
- Defaults to `true`
112+
- If set to `true`, the query will refetch on reconnect if the data is stale.
113+
- If set to `false`, the query will not refetch on reconnect.
114+
- If set to `"always"`, the query will always refetch on reconnect.
103115
- `notifyOnStatusChange: Boolean`
104116
- Optional
105117
- Set this to `false` to only re-render when there are changes to `data` or `error`.
@@ -134,10 +146,6 @@ const queryInfo = useQuery({
134146
- Optional
135147
- Defaults to `false`
136148
- Set this to `true` to always fetch when the component mounts (regardless of staleness).
137-
- `refetchOnMount: Boolean`
138-
- Optional
139-
- Defaults to `true`
140-
- If set to `false`, will disable additional instances of a query to trigger background refetches
141149
- `queryFnParamsFilter: Function(args) => filteredArgs`
142150
- Optional
143151
- This function will filter the params that get passed to `queryFn`.

src/core/query.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -234,13 +234,19 @@ export class Query<TResult, TError> {
234234
onInteraction(type: 'focus' | 'online'): void {
235235
// Execute the first observer which is enabled,
236236
// stale and wants to refetch on this interaction.
237-
const staleObserver = this.observers.find(
238-
observer =>
239-
observer.getCurrentResult().isStale &&
240-
observer.config.enabled &&
241-
((observer.config.refetchOnWindowFocus && type === 'focus') ||
242-
(observer.config.refetchOnReconnect && type === 'online'))
243-
)
237+
const staleObserver = this.observers.find(observer => {
238+
const { config } = observer
239+
const { isStale } = observer.getCurrentResult()
240+
return (
241+
config.enabled &&
242+
((type === 'focus' &&
243+
(config.refetchOnWindowFocus === 'always' ||
244+
(config.refetchOnWindowFocus && isStale))) ||
245+
(type === 'online' &&
246+
(config.refetchOnReconnect === 'always' ||
247+
(config.refetchOnReconnect && isStale))))
248+
)
249+
})
244250

245251
if (staleObserver) {
246252
staleObserver.fetch()

src/core/queryObserver.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,10 @@ export class QueryObserver<TResult, TError> {
4242
this.listener = listener
4343
this.currentQuery.subscribeObserver(this)
4444

45-
if (this.config.enabled && this.config.forceFetchOnMount) {
45+
if (
46+
this.config.enabled &&
47+
(this.config.forceFetchOnMount || this.config.refetchOnMount === 'always')
48+
) {
4649
this.fetch()
4750
} else {
4851
this.optionalFetch()

src/core/types.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,20 +95,26 @@ export interface QueryObserverConfig<
9595
*/
9696
refetchIntervalInBackground?: boolean
9797
/**
98-
* Set this to `true` or `false` to enable/disable automatic refetching on window focus for this query.
98+
* If set to `true`, the query will refetch on window focus if the data is stale.
99+
* If set to `false`, the query will not refetch on window focus.
100+
* If set to `'always'`, the query will always refetch on window focus.
99101
* Defaults to `true`.
100102
*/
101-
refetchOnWindowFocus?: boolean
103+
refetchOnWindowFocus?: boolean | 'always'
102104
/**
103-
* Set this to `true` or `false` to enable/disable automatic refetching on reconnect for this query.
105+
* If set to `true`, the query will refetch on reconnect if the data is stale.
106+
* If set to `false`, the query will not refetch on reconnect.
107+
* If set to `'always'`, the query will always refetch on reconnect.
104108
* Defaults to `true`.
105109
*/
106-
refetchOnReconnect?: boolean
110+
refetchOnReconnect?: boolean | 'always'
107111
/**
112+
* If set to `true`, the query will refetch on mount if the data is stale.
108113
* If set to `false`, will disable additional instances of a query to trigger background refetches.
114+
* If set to `'always'`, the query will always refetch on mount.
109115
* Defaults to `true`.
110116
*/
111-
refetchOnMount?: boolean
117+
refetchOnMount?: boolean | 'always'
112118
/**
113119
* Set this to `true` to always fetch when the component mounts (regardless of staleness).
114120
* Defaults to `false`.

src/react/tests/useQuery.test.tsx

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1129,6 +1129,171 @@ describe('useQuery', () => {
11291129
expect(queryFn).not.toHaveBeenCalled()
11301130
})
11311131

1132+
it('should not refetch stale query on focus when `refetchOnWindowFocus` is set to `false`', async () => {
1133+
const key = queryKey()
1134+
const states: QueryResult<number>[] = []
1135+
let count = 0
1136+
1137+
function Page() {
1138+
const state = useQuery(key, () => count++, {
1139+
staleTime: 0,
1140+
refetchOnWindowFocus: false,
1141+
})
1142+
states.push(state)
1143+
return null
1144+
}
1145+
1146+
render(<Page />)
1147+
1148+
await waitForMs(10)
1149+
1150+
act(() => {
1151+
window.dispatchEvent(new FocusEvent('focus'))
1152+
})
1153+
1154+
await waitForMs(10)
1155+
1156+
expect(states.length).toBe(2)
1157+
expect(states[0]).toMatchObject({ data: undefined, isFetching: true })
1158+
expect(states[1]).toMatchObject({ data: 0, isFetching: false })
1159+
})
1160+
1161+
it('should not refetch fresh query on focus when `refetchOnWindowFocus` is set to `true`', async () => {
1162+
const key = queryKey()
1163+
const states: QueryResult<number>[] = []
1164+
let count = 0
1165+
1166+
function Page() {
1167+
const state = useQuery(key, () => count++, {
1168+
staleTime: Infinity,
1169+
refetchOnWindowFocus: true,
1170+
})
1171+
states.push(state)
1172+
return null
1173+
}
1174+
1175+
render(<Page />)
1176+
1177+
await waitForMs(10)
1178+
1179+
act(() => {
1180+
window.dispatchEvent(new FocusEvent('focus'))
1181+
})
1182+
1183+
await waitForMs(10)
1184+
1185+
expect(states.length).toBe(2)
1186+
expect(states[0]).toMatchObject({ data: undefined, isFetching: true })
1187+
expect(states[1]).toMatchObject({ data: 0, isFetching: false })
1188+
})
1189+
1190+
it('should refetch fresh query on focus when `refetchOnWindowFocus` is set to `always`', async () => {
1191+
const key = queryKey()
1192+
const states: QueryResult<number>[] = []
1193+
let count = 0
1194+
1195+
function Page() {
1196+
const state = useQuery(key, () => count++, {
1197+
staleTime: Infinity,
1198+
refetchOnWindowFocus: 'always',
1199+
})
1200+
states.push(state)
1201+
return null
1202+
}
1203+
1204+
render(<Page />)
1205+
1206+
await waitForMs(10)
1207+
1208+
act(() => {
1209+
window.dispatchEvent(new FocusEvent('focus'))
1210+
})
1211+
1212+
await waitForMs(10)
1213+
1214+
expect(states.length).toBe(4)
1215+
expect(states[0]).toMatchObject({ data: undefined, isFetching: true })
1216+
expect(states[1]).toMatchObject({ data: 0, isFetching: false })
1217+
expect(states[2]).toMatchObject({ data: 0, isFetching: true })
1218+
expect(states[3]).toMatchObject({ data: 1, isFetching: false })
1219+
})
1220+
1221+
it('should refetch fresh query when refetchOnMount is set to always', async () => {
1222+
const key = queryKey()
1223+
const states: QueryResult<string>[] = []
1224+
1225+
await queryCache.prefetchQuery(key, () => 'prefetched')
1226+
1227+
function Page() {
1228+
const state = useQuery(key, () => 'data', {
1229+
refetchOnMount: 'always',
1230+
staleTime: Infinity,
1231+
})
1232+
states.push(state)
1233+
return null
1234+
}
1235+
1236+
render(<Page />)
1237+
1238+
await waitForMs(10)
1239+
1240+
expect(states.length).toBe(3)
1241+
expect(states[0]).toMatchObject({
1242+
data: 'prefetched',
1243+
isStale: false,
1244+
isFetching: false,
1245+
})
1246+
expect(states[1]).toMatchObject({
1247+
data: 'prefetched',
1248+
isStale: false,
1249+
isFetching: true,
1250+
})
1251+
expect(states[2]).toMatchObject({
1252+
data: 'data',
1253+
isStale: false,
1254+
isFetching: false,
1255+
})
1256+
})
1257+
1258+
it('should refetch stale query when refetchOnMount is set to true', async () => {
1259+
const key = queryKey()
1260+
const states: QueryResult<string>[] = []
1261+
1262+
await queryCache.prefetchQuery(key, () => 'prefetched')
1263+
1264+
await sleep(10)
1265+
1266+
function Page() {
1267+
const state = useQuery(key, () => 'data', {
1268+
refetchOnMount: true,
1269+
staleTime: 0,
1270+
})
1271+
states.push(state)
1272+
return null
1273+
}
1274+
1275+
render(<Page />)
1276+
1277+
await waitForMs(10)
1278+
1279+
expect(states.length).toBe(3)
1280+
expect(states[0]).toMatchObject({
1281+
data: 'prefetched',
1282+
isStale: true,
1283+
isFetching: false,
1284+
})
1285+
expect(states[1]).toMatchObject({
1286+
data: 'prefetched',
1287+
isStale: true,
1288+
isFetching: true,
1289+
})
1290+
expect(states[2]).toMatchObject({
1291+
data: 'data',
1292+
isStale: true,
1293+
isFetching: false,
1294+
})
1295+
})
1296+
11321297
it('should set status to error if queryFn throws', async () => {
11331298
const key = queryKey()
11341299
const consoleMock = mockConsoleError()

0 commit comments

Comments
 (0)