Skip to content

Commit 6b3840b

Browse files
committed
feat: add ability to manually control the online and focus state
1 parent 5c6cce6 commit 6b3840b

File tree

7 files changed

+203
-26
lines changed

7 files changed

+203
-26
lines changed

docs/src/manifests/manifest.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,16 @@
367367
"path": "/reference/useQueryErrorResetBoundary",
368368
"editUrl": "/reference/useQueryErrorResetBoundary.md"
369369
},
370+
{
371+
"title": "focusManager",
372+
"path": "/reference/focusManager",
373+
"editUrl": "/reference/focusManager.md"
374+
},
375+
{
376+
"title": "onlineManager",
377+
"path": "/reference/onlineManager",
378+
"editUrl": "/reference/onlineManager.md"
379+
},
370380
{
371381
"title": "setLogger",
372382
"path": "/reference/setLogger",

docs/src/pages/guides/window-focus-refetching.md

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ useQuery('todos', fetchTodos, { refetchOnWindowFocus: false })
3030

3131
## Custom Window Focus Event
3232

33-
In rare circumstances, you may want to manage your own window focus events that trigger React Query to revalidate. To do this, React Query provides a `focusManager.setHandler` function that supplies you the callback that should be fired when the window is focused and allows you to set up your own events. When calling `focusManager.setHandler`, the previously set handler is removed (which in most cases will be the default handler) and your new handler is used instead. For example, this is the default handler:
33+
In rare circumstances, you may want to manage your own window focus events that trigger React Query to revalidate. To do this, React Query provides a `focusManager.setEventListener` function that supplies you the callback that should be fired when the window is focused and allows you to set up your own events. When calling `focusManager.setEventListener`, the previously set handler is removed (which in most cases will be the default handler) and your new handler is used instead. For example, this is the default handler:
3434

3535
```js
36-
focusManager.setHandler(handleFocus => {
36+
focusManager.setEventListener(handleFocus => {
3737
// Listen to visibillitychange and focus
3838
if (typeof window !== 'undefined' && window.addEventListener) {
3939
window.addEventListener('visibilitychange', handleFocus, false)
@@ -56,7 +56,7 @@ A great use-case for replacing the focus handler is that of iframe events. Ifram
5656
import { focusManager } from 'react-query'
5757
import onWindowFocus from './onWindowFocus' // The gist above
5858

59-
focusManager.setHandler(onWindowFocus) // Boom!
59+
focusManager.setEventListener(onWindowFocus) // Boom!
6060
```
6161

6262
## Managing Focus in React Native
@@ -67,7 +67,7 @@ Instead of event listeners on `window`, React Native provides focus information
6767
import { focusManager } from 'react-query'
6868
import { AppState } from 'react-native'
6969

70-
focusManager.setHandler(handleFocus => {
70+
focusManager.setEventListener(handleFocus => {
7171
const handleAppStateChange = appState => {
7272
if (appState === 'active') {
7373
handleFocus()
@@ -77,3 +77,15 @@ focusManager.setHandler(handleFocus => {
7777
return () => AppState.removeEventListener('change', handleAppStateChange)
7878
})
7979
```
80+
81+
## Managing focus state
82+
83+
```js
84+
import { focusManager } from 'react-query'
85+
86+
// Override the default focus state
87+
focusManager.setFocused(true)
88+
89+
// Fallback to the default focus check
90+
focusManager.setFocused(undefined)
91+
```
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
---
2+
id: FocusManager
3+
title: FocusManager
4+
---
5+
6+
The `FocusManager` manages the focus state within React Query.
7+
8+
It can be used to change the default event listeners or to manually change the focus state.
9+
10+
Its available methods are:
11+
12+
- [`setEventListener`](#focusmanagerseteventlistener)
13+
- [`setFocused`](#focusmanagersetfocused)
14+
- [`isFocused`](#focusmanagerisfocused)
15+
16+
## `focusManager.setEventListener`
17+
18+
`setEventListener` can be used to set a custom event listener:
19+
20+
```js
21+
import { focusManager } from 'react-query'
22+
23+
focusManager.setEventListener(handleFocus => {
24+
// Listen to visibillitychange and focus
25+
if (typeof window !== 'undefined' && window.addEventListener) {
26+
window.addEventListener('visibilitychange', handleFocus, false)
27+
window.addEventListener('focus', handleFocus, false)
28+
}
29+
30+
return () => {
31+
// Be sure to unsubscribe if a new handler is set
32+
window.removeEventListener('visibilitychange', handleFocus)
33+
window.removeEventListener('focus', handleFocus)
34+
}
35+
})
36+
```
37+
38+
## `focusManager.setFocused`
39+
40+
`setFocsued` can be used to manually set the focus state. Set `undefined` to fallback to the default focus check.
41+
42+
```js
43+
import { focusManager } from 'react-query'
44+
45+
// Set focused
46+
focusManager.setFocused(true)
47+
48+
// Set unfocused
49+
focusManager.setFocused(false)
50+
51+
// Fallback to the default focus check
52+
focusManager.setFocused(undefined)
53+
```
54+
55+
**Options**
56+
57+
- `focused: boolean | undefined`
58+
59+
## `focusManager.isFocused`
60+
61+
`isFocused` can be used to get the current focus state.
62+
63+
```js
64+
const isFocused = focusManager.isFocused()
65+
```
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
---
2+
id: OnlineManager
3+
title: OnlineManager
4+
---
5+
6+
The `OnlineManager` manages the online state within React Query.
7+
8+
It can be used to change the default event listeners or to manually change the online state.
9+
10+
Its available methods are:
11+
12+
- [`setEventListener`](#onlinemanagerseteventlistener)
13+
- [`setOnline`](#onlinemanagersetonline)
14+
- [`isOnline`](#onlinemanagerisonline)
15+
16+
## `onlineManager.setEventListener`
17+
18+
`setEventListener` can be used to set a custom event listener:
19+
20+
```js
21+
import { onlineManager } from 'react-query'
22+
23+
onlineManager.setEventListener(handleOnline => {
24+
// Listen to visibillitychange and online
25+
if (typeof window !== 'undefined' && window.addEventListener) {
26+
window.addEventListener('visibilitychange', handleOnline, false)
27+
window.addEventListener('online', handleOnline, false)
28+
}
29+
30+
return () => {
31+
// Be sure to unsubscribe if a new handler is set
32+
window.removeEventListener('visibilitychange', handleOnline)
33+
window.removeEventListener('online', handleOnline)
34+
}
35+
})
36+
```
37+
38+
## `onlineManager.setOnline`
39+
40+
`setOnline` can be used to manually set the online state. Set `undefined` to fallback to the default online check.
41+
42+
```js
43+
import { onlineManager } from 'react-query'
44+
45+
// Set to online
46+
onlineManager.setOnline(true)
47+
48+
// Set to offline
49+
onlineManager.setOnline(false)
50+
51+
// Fallback to the default online check
52+
onlineManager.setOnline(undefined)
53+
```
54+
55+
**Options**
56+
57+
- `online: boolean | undefined`
58+
59+
## `onlineManager.isOnline`
60+
61+
`isOnline` can be used to get the current online state.
62+
63+
```js
64+
const isOnline = onlineManager.isOnline()
65+
```

src/core/focusManager.ts

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,20 @@ import { Subscribable } from './subscribable'
22
import { isServer } from './utils'
33

44
class FocusManager extends Subscribable {
5-
private removeHandler?: () => void
5+
private focused?: boolean
6+
private removeEventListener?: () => void
67

78
protected onSubscribe(): void {
8-
if (!this.removeHandler) {
9-
this.setDefaultHandler()
9+
if (!this.removeEventListener) {
10+
this.setDefaultEventListener()
1011
}
1112
}
1213

13-
setHandler(init: (onFocus: () => void) => () => void): void {
14-
if (this.removeHandler) {
15-
this.removeHandler()
14+
setEventListener(setup: (onFocus: () => void) => () => void): void {
15+
if (this.removeEventListener) {
16+
this.removeEventListener()
1617
}
17-
this.removeHandler = init(() => {
18+
this.removeEventListener = setup(() => {
1819
this.onFocus()
1920
})
2021
}
@@ -25,7 +26,19 @@ class FocusManager extends Subscribable {
2526
})
2627
}
2728

29+
setFocused(focused: boolean | undefined): void {
30+
this.focused = focused
31+
32+
if (focused) {
33+
this.onFocus()
34+
}
35+
}
36+
2837
isFocused(): boolean {
38+
if (typeof this.focused === 'boolean') {
39+
return this.focused
40+
}
41+
2942
// document global can be unavailable in react native
3043
if (typeof document === 'undefined') {
3144
return true
@@ -36,9 +49,9 @@ class FocusManager extends Subscribable {
3649
)
3750
}
3851

39-
private setDefaultHandler() {
52+
private setDefaultEventListener() {
4053
if (!isServer && window?.addEventListener) {
41-
this.setHandler(onFocus => {
54+
this.setEventListener(onFocus => {
4255
// Listen to visibillitychange and focus
4356
window.addEventListener('visibilitychange', onFocus, false)
4457
window.addEventListener('focus', onFocus, false)

src/core/onlineManager.ts

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,20 @@ import { Subscribable } from './subscribable'
22
import { isServer } from './utils'
33

44
class OnlineManager extends Subscribable {
5-
private removeHandler?: () => void
5+
private online?: boolean
6+
private removeEventListener?: () => void
67

78
protected onSubscribe(): void {
8-
if (!this.removeHandler) {
9-
this.setDefaultHandler()
9+
if (!this.removeEventListener) {
10+
this.setDefaultEventListener()
1011
}
1112
}
1213

13-
setHandler(init: (onOnline: () => void) => () => void): void {
14-
if (this.removeHandler) {
15-
this.removeHandler()
14+
setEventListener(setup: (onOnline: () => void) => () => void): void {
15+
if (this.removeEventListener) {
16+
this.removeEventListener()
1617
}
17-
this.removeHandler = init(() => {
18+
this.removeEventListener = setup(() => {
1819
this.onOnline()
1920
})
2021
}
@@ -25,13 +26,25 @@ class OnlineManager extends Subscribable {
2526
})
2627
}
2728

29+
setOnline(online: boolean | undefined): void {
30+
this.online = online
31+
32+
if (online) {
33+
this.onOnline()
34+
}
35+
}
36+
2837
isOnline(): boolean {
38+
if (typeof this.online === 'boolean') {
39+
return this.online
40+
}
41+
2942
return navigator.onLine === undefined || navigator.onLine
3043
}
3144

32-
private setDefaultHandler() {
45+
private setDefaultEventListener() {
3346
if (!isServer && window?.addEventListener) {
34-
this.setHandler(onOnline => {
47+
this.setEventListener(onOnline => {
3548
// Listen to online
3649
window.addEventListener('online', onOnline, false)
3750

src/core/tests/queryCache.test.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import {
33
queryKey,
44
mockVisibilityState,
55
mockConsoleError,
6-
mockNavigatorOnLine,
76
expectType,
87
} from '../../react/tests/utils'
98
import {
@@ -12,6 +11,7 @@ import {
1211
QueryObserver,
1312
isCancelledError,
1413
isError,
14+
onlineManager,
1515
} from '../..'
1616
import { QueryObserverResult } from '../types'
1717
import { QueriesObserver } from '../queriesObserver'
@@ -1172,7 +1172,7 @@ describe('queryCache', () => {
11721172
it('should continue retry after reconnect and resolve all promises', async () => {
11731173
const key = queryKey()
11741174

1175-
mockNavigatorOnLine(false)
1175+
onlineManager.setOnline(false)
11761176

11771177
let count = 0
11781178
let result
@@ -1206,8 +1206,7 @@ describe('queryCache', () => {
12061206
expect(result).toBeUndefined()
12071207

12081208
// Reset navigator to original value
1209-
mockNavigatorOnLine(true)
1210-
window.dispatchEvent(new Event('online'))
1209+
onlineManager.setOnline(true)
12111210

12121211
// There should not be a result yet
12131212
expect(result).toBeUndefined()

0 commit comments

Comments
 (0)