Skip to content

Commit f4dfe7b

Browse files
committed
fix: cancellations for useInfiniteQuery
1 parent 05b2c2d commit f4dfe7b

File tree

4 files changed

+107
-3
lines changed

4 files changed

+107
-3
lines changed

src/core/infiniteQueryBehavior.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,23 @@ export function infiniteQueryBehavior<
3737
pageParam: param,
3838
}
3939

40-
return Promise.resolve()
41-
.then(() => queryFn(queryFnContext))
40+
let cancelFn: undefined | (() => any)
41+
const queryFnResult = queryFn(queryFnContext)
42+
if ('cancel' in queryFnResult) {
43+
cancelFn = (queryFnResult as any).cancel
44+
}
45+
46+
const promise = Promise.resolve(queryFnResult)
4247
.then(page => {
4348
newPageParams = previous
4449
? [param, ...newPageParams]
4550
: [...newPageParams, param]
4651
return previous ? [page, ...pages] : [...pages, page]
4752
})
53+
if (cancelFn) {
54+
(promise as any).cancel = cancelFn
55+
}
56+
return promise
4857
}
4958

5059
let promise
@@ -92,7 +101,11 @@ export function infiniteQueryBehavior<
92101
}
93102
}
94103

95-
return promise.then(pages => ({ pages, pageParams: newPageParams }))
104+
const finalPromise = promise.then(pages => ({ pages, pageParams: newPageParams }))
105+
if ('cancel' in promise) {
106+
(finalPromise as any).cancel = (promise as any).cancel;
107+
}
108+
return finalPromise;
96109
}
97110
},
98111
}

src/react/tests/useInfiniteQuery.test.tsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
mockConsoleError,
88
renderWithClient,
99
setActTimeout,
10+
Blink,
1011
} from './utils'
1112
import {
1213
useInfiniteQuery,
@@ -1249,4 +1250,40 @@ describe('useInfiniteQuery', () => {
12491250

12501251
rendered.getByText('Nothing more to load')
12511252
})
1253+
1254+
it('should cancel the query function when there are no more subscriptions', async () => {
1255+
const key = queryKey()
1256+
let cancelFn: jest.Mock = jest.fn()
1257+
1258+
const queryFn = () => {
1259+
const promise = new Promise<string>((resolve, reject) => {
1260+
cancelFn = jest.fn(() => reject('Cancelled'))
1261+
sleep(10).then(() => resolve('OK'))
1262+
})
1263+
1264+
;(promise as any).cancel = cancelFn
1265+
1266+
return promise
1267+
}
1268+
1269+
function Page() {
1270+
const state = useInfiniteQuery(key, queryFn)
1271+
return (
1272+
<div>
1273+
<h1>Status: {state.status}</h1>
1274+
</div>
1275+
)
1276+
}
1277+
1278+
const rendered = renderWithClient(
1279+
queryClient,
1280+
<Blink duration={5}>
1281+
<Page />
1282+
</Blink>
1283+
)
1284+
1285+
await waitFor(() => rendered.getByText('off'))
1286+
1287+
expect(cancelFn).toHaveBeenCalled()
1288+
})
12521289
})

src/react/tests/useQuery.test.tsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
sleep,
1010
renderWithClient,
1111
setActTimeout,
12+
Blink,
1213
} from './utils'
1314
import {
1415
useQuery,
@@ -2815,4 +2816,40 @@ describe('useQuery', () => {
28152816
},
28162817
])
28172818
})
2819+
2820+
it('should cancel the query function when there are no more subscriptions', async () => {
2821+
const key = queryKey()
2822+
let cancelFn: jest.Mock = jest.fn()
2823+
2824+
const queryFn = () => {
2825+
const promise = new Promise<string>((resolve, reject) => {
2826+
cancelFn = jest.fn(() => reject('Cancelled'))
2827+
sleep(10).then(() => resolve('OK'))
2828+
})
2829+
2830+
;(promise as any).cancel = cancelFn
2831+
2832+
return promise
2833+
}
2834+
2835+
function Page() {
2836+
const state = useQuery(key, queryFn)
2837+
return (
2838+
<div>
2839+
<h1>Status: {state.status}</h1>
2840+
</div>
2841+
)
2842+
}
2843+
2844+
const rendered = renderWithClient(
2845+
queryClient,
2846+
<Blink duration={5}>
2847+
<Page />
2848+
</Blink>
2849+
)
2850+
2851+
await waitFor(() => rendered.getByText('off'))
2852+
2853+
expect(cancelFn).toHaveBeenCalled()
2854+
})
28182855
})

src/react/tests/utils.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,20 @@ export function setActTimeout(fn: () => void, ms?: number) {
5151
* Assert the parameter is of a specific type.
5252
*/
5353
export const expectType = <T,>(_: T): void => undefined
54+
55+
export const Blink: React.FC<{ duration: number }> = ({
56+
duration,
57+
children,
58+
}) => {
59+
const [shouldShow, setShouldShow] = React.useState<boolean>(true)
60+
61+
React.useEffect(() => {
62+
setShouldShow(true)
63+
const timeout = setTimeout(() => setShouldShow(false), duration)
64+
return () => {
65+
clearTimeout(timeout)
66+
}
67+
}, [duration, children])
68+
69+
return shouldShow ? <>{children}</> : <>off</>
70+
}

0 commit comments

Comments
 (0)