Skip to content

Commit

Permalink
Merge pull request #4055 from riqts/Pause-Polling-When-Unfocused
Browse files Browse the repository at this point in the history
Option for queries to pause polling when unfocused
  • Loading branch information
EskiMojo14 committed Jan 24, 2024
2 parents 6041460 + 8cbf2a1 commit bb28db3
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 9 deletions.
8 changes: 8 additions & 0 deletions packages/toolkit/src/query/core/apiState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,14 @@ export type SubscriptionOptions = {
* How frequently to automatically re-fetch data (in milliseconds). Defaults to `0` (off).
*/
pollingInterval?: number
/**
* Defaults to 'false'. This setting allows you to control whether RTK Query will continue polling if the window is not focused.
*
* If pollingInterval is not set or set to 0, this **will not be evaluated** until pollingInterval is greater than 0.
*
* Note: requires [`setupListeners`](./setupListeners) to have been called.
*/
skipPollingIfUnfocused?: boolean
/**
* Defaults to `false`. This setting allows you to control whether RTK Query will try to refetch all subscribed queries after regaining a network connection.
*
Expand Down
26 changes: 17 additions & 9 deletions packages/toolkit/src/query/core/buildMiddleware/polling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ export const buildPollingHandler: InternalHandlerBuilder = ({
if (!querySubState || querySubState.status === QueryStatus.uninitialized)
return

const lowestPollingInterval = findLowestPollingInterval(subscriptions)
const { lowestPollingInterval, skipPollingIfUnfocused } =
findLowestPollingInterval(subscriptions)
if (!Number.isFinite(lowestPollingInterval)) return

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

const nextPollTimestamp = Date.now() + lowestPollingInterval

const currentInterval: typeof currentPolls[number] = (currentPolls[
queryCacheKey
] = {
currentPolls[queryCacheKey] = {
nextPollTimestamp,
pollingInterval: lowestPollingInterval,
timeout: setTimeout(() => {
currentInterval!.timeout = undefined
api.dispatch(refetchQuery(querySubState, queryCacheKey))
if (state.config.focused || !skipPollingIfUnfocused) {
api.dispatch(refetchQuery(querySubState, queryCacheKey))
}
startNextPoll({ queryCacheKey }, api)
}, lowestPollingInterval),
})
}
}

function updatePollingInterval(
Expand All @@ -96,7 +97,7 @@ export const buildPollingHandler: InternalHandlerBuilder = ({
return
}

const lowestPollingInterval = findLowestPollingInterval(subscriptions)
const { lowestPollingInterval } = findLowestPollingInterval(subscriptions)

if (!Number.isFinite(lowestPollingInterval)) {
cleanupPollForKey(queryCacheKey)
Expand Down Expand Up @@ -126,17 +127,24 @@ export const buildPollingHandler: InternalHandlerBuilder = ({
}

function findLowestPollingInterval(subscribers: Subscribers = {}) {
let skipPollingIfUnfocused: boolean | undefined = false
let lowestPollingInterval = Number.POSITIVE_INFINITY
for (let key in subscribers) {
if (!!subscribers[key].pollingInterval) {
lowestPollingInterval = Math.min(
subscribers[key].pollingInterval!,
lowestPollingInterval
)
skipPollingIfUnfocused =
subscribers[key].skipPollingIfUnfocused || skipPollingIfUnfocused
}
}

return lowestPollingInterval
return {
lowestPollingInterval,
skipPollingIfUnfocused,
}
}

return handler
}
4 changes: 4 additions & 0 deletions packages/toolkit/src/query/react/buildHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,7 @@ export function buildHooks<Definitions extends EndpointDefinitions>({
refetchOnMountOrArgChange,
skip = false,
pollingInterval = 0,
skipPollingIfUnfocused = false,
} = {}
) => {
const { initiate } = api.endpoints[name] as ApiEndpointQuery<
Expand Down Expand Up @@ -715,6 +716,7 @@ export function buildHooks<Definitions extends EndpointDefinitions>({
refetchOnReconnect,
refetchOnFocus,
pollingInterval,
skipPollingIfUnfocused,
})

const lastRenderHadSubscription = useRef(false)
Expand Down Expand Up @@ -815,6 +817,7 @@ export function buildHooks<Definitions extends EndpointDefinitions>({
refetchOnReconnect,
refetchOnFocus,
pollingInterval = 0,
skipPollingIfUnfocused = false,
} = {}) => {
const { initiate } = api.endpoints[name] as ApiEndpointQuery<
QueryDefinition<any, any, any, any, any>,
Expand All @@ -829,6 +832,7 @@ export function buildHooks<Definitions extends EndpointDefinitions>({
refetchOnReconnect,
refetchOnFocus,
pollingInterval,
skipPollingIfUnfocused,
})

usePossiblyImmediateEffect(() => {
Expand Down
108 changes: 108 additions & 0 deletions packages/toolkit/src/query/tests/polling.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,112 @@ describe('polling tests', () => {

expect(mockBaseQuery.mock.calls.length).toBeGreaterThanOrEqual(2)
})

it('respects skipPollingIfUnfocused', async () => {
mockBaseQuery.mockClear()
storeRef.store.dispatch(
getPosts.initiate(2, {
subscriptionOptions: {
pollingInterval: 10,
skipPollingIfUnfocused: true,
},
subscribe: true,
})
)
storeRef.store.dispatch(api.internalActions?.onFocusLost())

await delay(50)
const callsWithSkip = mockBaseQuery.mock.calls.length

storeRef.store.dispatch(
getPosts.initiate(2, {
subscriptionOptions: {
pollingInterval: 10,
skipPollingIfUnfocused: false,
},
subscribe: true,
})
)

storeRef.store.dispatch(api.internalActions?.onFocus())

await delay(50)
const callsWithoutSkip = mockBaseQuery.mock.calls.length

expect(callsWithSkip).toBe(1)
expect(callsWithoutSkip).toBeGreaterThan(2)

storeRef.store.dispatch(api.util.resetApiState())
})

it('respects skipPollingIfUnfocused if at least one subscription has it', async () => {
storeRef.store.dispatch(
getPosts.initiate(3, {
subscriptionOptions: {
pollingInterval: 10,
skipPollingIfUnfocused: false,
},
subscribe: true,
})
)

await delay(50)
const callsWithoutSkip = mockBaseQuery.mock.calls.length

storeRef.store.dispatch(
getPosts.initiate(3, {
subscriptionOptions: {
pollingInterval: 15,
skipPollingIfUnfocused: true,
},
subscribe: true,
})
)

storeRef.store.dispatch(
getPosts.initiate(3, {
subscriptionOptions: {
pollingInterval: 20,
skipPollingIfUnfocused: false,
},
subscribe: true,
})
)

storeRef.store.dispatch(api.internalActions?.onFocusLost())

await delay(50)
const callsWithSkip = mockBaseQuery.mock.calls.length

expect(callsWithoutSkip).toBeGreaterThan(2)
expect(callsWithSkip).toBe(callsWithoutSkip + 1)
})

it('replaces skipPollingIfUnfocused when the subscription options are updated', async () => {
const { requestId, queryCacheKey, ...subscription } =
storeRef.store.dispatch(
getPosts.initiate(1, {
subscriptionOptions: {
pollingInterval: 10,
skipPollingIfUnfocused: false,
},
subscribe: true,
})
)

const getSubs = createSubscriptionGetter(queryCacheKey)

await delay(1)
expect(Object.keys(getSubs())).toHaveLength(1)
expect(getSubs()[requestId].skipPollingIfUnfocused).toBe(false)

subscription.updateSubscriptionOptions({
pollingInterval: 20,
skipPollingIfUnfocused: true,
})

await delay(1)
expect(Object.keys(getSubs())).toHaveLength(1)
expect(getSubs()[requestId].skipPollingIfUnfocused).toBe(true)
})
})

0 comments on commit bb28db3

Please sign in to comment.