Skip to content

Commit 43b5f7e

Browse files
committed
Merge branch 'master' of https://github.com/reduxjs/redux-toolkit into fix-type-tests-setup
2 parents 8597a71 + f5b07fe commit 43b5f7e

File tree

6 files changed

+142
-10
lines changed

6 files changed

+142
-10
lines changed

docs/api/configureStore.mdx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ interface ConfigureStoreOptions<
4646
P = S
4747
> {
4848
/**
49+
* A single reducer function that will be used as the root reducer, or an
50+
* object of slice reducers that will be passed to `combineReducers()`.
51+
*/
52+
reducer: Reducer<S, A, P> | ReducersMapObject<S, A, P>
4953

5054
/**
5155
* An array of Redux middleware to install. If not supplied, defaults to

packages/toolkit/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@reduxjs/toolkit",
3-
"version": "2.0.2",
3+
"version": "2.1.0",
44
"description": "The official, opinionated, batteries-included toolset for efficient Redux development",
55
"author": "Mark Erikson <mark@isquaredsoftware.com>",
66
"license": "MIT",

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
@@ -670,6 +670,7 @@ export function buildHooks<Definitions extends EndpointDefinitions>({
670670
refetchOnMountOrArgChange,
671671
skip = false,
672672
pollingInterval = 0,
673+
skipPollingIfUnfocused = false,
673674
} = {}
674675
) => {
675676
const { initiate } = api.endpoints[name] as ApiEndpointQuery<
@@ -713,6 +714,7 @@ export function buildHooks<Definitions extends EndpointDefinitions>({
713714
refetchOnReconnect,
714715
refetchOnFocus,
715716
pollingInterval,
717+
skipPollingIfUnfocused,
716718
})
717719

718720
const lastRenderHadSubscription = useRef(false)
@@ -813,6 +815,7 @@ export function buildHooks<Definitions extends EndpointDefinitions>({
813815
refetchOnReconnect,
814816
refetchOnFocus,
815817
pollingInterval = 0,
818+
skipPollingIfUnfocused = false,
816819
} = {}) => {
817820
const { initiate } = api.endpoints[name] as ApiEndpointQuery<
818821
QueryDefinition<any, any, any, any, any>,
@@ -827,6 +830,7 @@ export function buildHooks<Definitions extends EndpointDefinitions>({
827830
refetchOnReconnect,
828831
refetchOnFocus,
829832
pollingInterval,
833+
skipPollingIfUnfocused,
830834
})
831835

832836
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)