Skip to content

Commit bb28db3

Browse files
authored
Merge pull request #4055 from riqts/Pause-Polling-When-Unfocused
Option for queries to pause polling when unfocused
2 parents 6041460 + 8cbf2a1 commit bb28db3

File tree

4 files changed

+137
-9
lines changed

4 files changed

+137
-9
lines changed

packages/toolkit/src/query/core/apiState.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,14 @@ export type SubscriptionOptions = {
8383
* How frequently to automatically re-fetch data (in milliseconds). Defaults to `0` (off).
8484
*/
8585
pollingInterval?: number
86+
/**
87+
* Defaults to 'false'. This setting allows you to control whether RTK Query will continue polling if the window is not focused.
88+
*
89+
* If pollingInterval is not set or set to 0, this **will not be evaluated** until pollingInterval is greater than 0.
90+
*
91+
* Note: requires [`setupListeners`](./setupListeners) to have been called.
92+
*/
93+
skipPollingIfUnfocused?: boolean
8694
/**
8795
* Defaults to `false`. This setting allows you to control whether RTK Query will try to refetch all subscribed queries after regaining a network connection.
8896
*

packages/toolkit/src/query/core/buildMiddleware/polling.ts

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ export const buildPollingHandler: InternalHandlerBuilder = ({
6060
if (!querySubState || querySubState.status === QueryStatus.uninitialized)
6161
return
6262

63-
const lowestPollingInterval = findLowestPollingInterval(subscriptions)
63+
const { lowestPollingInterval, skipPollingIfUnfocused } =
64+
findLowestPollingInterval(subscriptions)
6465
if (!Number.isFinite(lowestPollingInterval)) return
6566

6667
const currentPoll = currentPolls[queryCacheKey]
@@ -72,16 +73,16 @@ export const buildPollingHandler: InternalHandlerBuilder = ({
7273

7374
const nextPollTimestamp = Date.now() + lowestPollingInterval
7475

75-
const currentInterval: typeof currentPolls[number] = (currentPolls[
76-
queryCacheKey
77-
] = {
76+
currentPolls[queryCacheKey] = {
7877
nextPollTimestamp,
7978
pollingInterval: lowestPollingInterval,
8079
timeout: setTimeout(() => {
81-
currentInterval!.timeout = undefined
82-
api.dispatch(refetchQuery(querySubState, queryCacheKey))
80+
if (state.config.focused || !skipPollingIfUnfocused) {
81+
api.dispatch(refetchQuery(querySubState, queryCacheKey))
82+
}
83+
startNextPoll({ queryCacheKey }, api)
8384
}, lowestPollingInterval),
84-
})
85+
}
8586
}
8687

8788
function updatePollingInterval(
@@ -96,7 +97,7 @@ export const buildPollingHandler: InternalHandlerBuilder = ({
9697
return
9798
}
9899

99-
const lowestPollingInterval = findLowestPollingInterval(subscriptions)
100+
const { lowestPollingInterval } = findLowestPollingInterval(subscriptions)
100101

101102
if (!Number.isFinite(lowestPollingInterval)) {
102103
cleanupPollForKey(queryCacheKey)
@@ -126,17 +127,24 @@ export const buildPollingHandler: InternalHandlerBuilder = ({
126127
}
127128

128129
function findLowestPollingInterval(subscribers: Subscribers = {}) {
130+
let skipPollingIfUnfocused: boolean | undefined = false
129131
let lowestPollingInterval = Number.POSITIVE_INFINITY
130132
for (let key in subscribers) {
131133
if (!!subscribers[key].pollingInterval) {
132134
lowestPollingInterval = Math.min(
133135
subscribers[key].pollingInterval!,
134136
lowestPollingInterval
135137
)
138+
skipPollingIfUnfocused =
139+
subscribers[key].skipPollingIfUnfocused || skipPollingIfUnfocused
136140
}
137141
}
138142

139-
return lowestPollingInterval
143+
return {
144+
lowestPollingInterval,
145+
skipPollingIfUnfocused,
146+
}
140147
}
148+
141149
return handler
142150
}

packages/toolkit/src/query/react/buildHooks.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -672,6 +672,7 @@ export function buildHooks<Definitions extends EndpointDefinitions>({
672672
refetchOnMountOrArgChange,
673673
skip = false,
674674
pollingInterval = 0,
675+
skipPollingIfUnfocused = false,
675676
} = {}
676677
) => {
677678
const { initiate } = api.endpoints[name] as ApiEndpointQuery<
@@ -715,6 +716,7 @@ export function buildHooks<Definitions extends EndpointDefinitions>({
715716
refetchOnReconnect,
716717
refetchOnFocus,
717718
pollingInterval,
719+
skipPollingIfUnfocused,
718720
})
719721

720722
const lastRenderHadSubscription = useRef(false)
@@ -815,6 +817,7 @@ export function buildHooks<Definitions extends EndpointDefinitions>({
815817
refetchOnReconnect,
816818
refetchOnFocus,
817819
pollingInterval = 0,
820+
skipPollingIfUnfocused = false,
818821
} = {}) => {
819822
const { initiate } = api.endpoints[name] as ApiEndpointQuery<
820823
QueryDefinition<any, any, any, any, any>,
@@ -829,6 +832,7 @@ export function buildHooks<Definitions extends EndpointDefinitions>({
829832
refetchOnReconnect,
830833
refetchOnFocus,
831834
pollingInterval,
835+
skipPollingIfUnfocused,
832836
})
833837

834838
usePossiblyImmediateEffect(() => {

packages/toolkit/src/query/tests/polling.test.tsx

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,4 +122,112 @@ describe('polling tests', () => {
122122

123123
expect(mockBaseQuery.mock.calls.length).toBeGreaterThanOrEqual(2)
124124
})
125+
126+
it('respects skipPollingIfUnfocused', async () => {
127+
mockBaseQuery.mockClear()
128+
storeRef.store.dispatch(
129+
getPosts.initiate(2, {
130+
subscriptionOptions: {
131+
pollingInterval: 10,
132+
skipPollingIfUnfocused: true,
133+
},
134+
subscribe: true,
135+
})
136+
)
137+
storeRef.store.dispatch(api.internalActions?.onFocusLost())
138+
139+
await delay(50)
140+
const callsWithSkip = mockBaseQuery.mock.calls.length
141+
142+
storeRef.store.dispatch(
143+
getPosts.initiate(2, {
144+
subscriptionOptions: {
145+
pollingInterval: 10,
146+
skipPollingIfUnfocused: false,
147+
},
148+
subscribe: true,
149+
})
150+
)
151+
152+
storeRef.store.dispatch(api.internalActions?.onFocus())
153+
154+
await delay(50)
155+
const callsWithoutSkip = mockBaseQuery.mock.calls.length
156+
157+
expect(callsWithSkip).toBe(1)
158+
expect(callsWithoutSkip).toBeGreaterThan(2)
159+
160+
storeRef.store.dispatch(api.util.resetApiState())
161+
})
162+
163+
it('respects skipPollingIfUnfocused if at least one subscription has it', async () => {
164+
storeRef.store.dispatch(
165+
getPosts.initiate(3, {
166+
subscriptionOptions: {
167+
pollingInterval: 10,
168+
skipPollingIfUnfocused: false,
169+
},
170+
subscribe: true,
171+
})
172+
)
173+
174+
await delay(50)
175+
const callsWithoutSkip = mockBaseQuery.mock.calls.length
176+
177+
storeRef.store.dispatch(
178+
getPosts.initiate(3, {
179+
subscriptionOptions: {
180+
pollingInterval: 15,
181+
skipPollingIfUnfocused: true,
182+
},
183+
subscribe: true,
184+
})
185+
)
186+
187+
storeRef.store.dispatch(
188+
getPosts.initiate(3, {
189+
subscriptionOptions: {
190+
pollingInterval: 20,
191+
skipPollingIfUnfocused: false,
192+
},
193+
subscribe: true,
194+
})
195+
)
196+
197+
storeRef.store.dispatch(api.internalActions?.onFocusLost())
198+
199+
await delay(50)
200+
const callsWithSkip = mockBaseQuery.mock.calls.length
201+
202+
expect(callsWithoutSkip).toBeGreaterThan(2)
203+
expect(callsWithSkip).toBe(callsWithoutSkip + 1)
204+
})
205+
206+
it('replaces skipPollingIfUnfocused when the subscription options are updated', async () => {
207+
const { requestId, queryCacheKey, ...subscription } =
208+
storeRef.store.dispatch(
209+
getPosts.initiate(1, {
210+
subscriptionOptions: {
211+
pollingInterval: 10,
212+
skipPollingIfUnfocused: false,
213+
},
214+
subscribe: true,
215+
})
216+
)
217+
218+
const getSubs = createSubscriptionGetter(queryCacheKey)
219+
220+
await delay(1)
221+
expect(Object.keys(getSubs())).toHaveLength(1)
222+
expect(getSubs()[requestId].skipPollingIfUnfocused).toBe(false)
223+
224+
subscription.updateSubscriptionOptions({
225+
pollingInterval: 20,
226+
skipPollingIfUnfocused: true,
227+
})
228+
229+
await delay(1)
230+
expect(Object.keys(getSubs())).toHaveLength(1)
231+
expect(getSubs()[requestId].skipPollingIfUnfocused).toBe(true)
232+
})
125233
})

0 commit comments

Comments
 (0)