Skip to content

Commit bdd2b55

Browse files
authored
fix(query-core): do not call resumePausedMutations while we are offline (#7019)
when mutations are resumed after they have been restored from an external storage, there is no retryer that can pick up the mutation where it left off, so we need to re-execute it from scratch. However, this doesn't really happen if we are offline - the promise will be pending until we go online again; only "continue" from the retryer can immediately resolve a Promise here - execute needs to wait until it's really finished. But since mutations run in serial when resumed, this will lead to a forever hanging promise. With this fix, `queryClient.resumePausedMutations` will only resume them if we are currently online to avoid this state.
1 parent aa9a789 commit bdd2b55

File tree

2 files changed

+84
-14
lines changed

2 files changed

+84
-14
lines changed

packages/query-core/src/queryClient.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ export class QueryClient {
8080
this.#queryCache.onFocus()
8181
}
8282
})
83-
this.#unsubscribeOnline = onlineManager.subscribe(() => {
84-
if (onlineManager.isOnline()) {
83+
this.#unsubscribeOnline = onlineManager.subscribe((online) => {
84+
if (online) {
8585
this.resumePausedMutations()
8686
this.#queryCache.onOnline()
8787
}
@@ -391,7 +391,10 @@ export class QueryClient {
391391
}
392392

393393
resumePausedMutations(): Promise<unknown> {
394-
return this.#mutationCache.resumePausedMutations()
394+
if (onlineManager.isOnline()) {
395+
return this.#mutationCache.resumePausedMutations()
396+
}
397+
return Promise.resolve()
395398
}
396399

397400
getQueryCache(): QueryCache {

packages/query-core/src/tests/queryClient.test.tsx

Lines changed: 78 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,20 @@ import { waitFor } from '@testing-library/react'
33

44
import {
55
MutationObserver,
6+
QueryClient,
67
QueryObserver,
8+
dehydrate,
79
focusManager,
10+
hydrate,
811
onlineManager,
912
} from '..'
10-
import { noop } from '../utils'
1113
import {
1214
createQueryClient,
1315
mockOnlineManagerIsOnline,
1416
queryKey,
1517
sleep,
1618
} from './utils'
17-
import type {
18-
QueryCache,
19-
QueryClient,
20-
QueryFunction,
21-
QueryObserverOptions,
22-
} from '..'
19+
import type { QueryCache, QueryFunction, QueryObserverOptions } from '..'
2320

2421
describe('queryClient', () => {
2522
let queryClient: QueryClient
@@ -1458,8 +1455,8 @@ describe('queryClient', () => {
14581455
const observer2 = new MutationObserver(queryClient, {
14591456
mutationFn: async () => 2,
14601457
})
1461-
void observer1.mutate().catch(noop)
1462-
void observer2.mutate().catch(noop)
1458+
void observer1.mutate()
1459+
void observer2.mutate()
14631460

14641461
await waitFor(() => {
14651462
expect(observer1.getCurrentResult().isPaused).toBeTruthy()
@@ -1500,8 +1497,8 @@ describe('queryClient', () => {
15001497
return 2
15011498
},
15021499
})
1503-
void observer1.mutate().catch(noop)
1504-
void observer2.mutate().catch(noop)
1500+
void observer1.mutate()
1501+
void observer2.mutate()
15051502

15061503
await waitFor(() => {
15071504
expect(observer1.getCurrentResult().isPaused).toBeTruthy()
@@ -1521,6 +1518,76 @@ describe('queryClient', () => {
15211518
expect(orders).toEqual(['1start', '1end', '2start', '2end'])
15221519
})
15231520

1521+
test('should resumePausedMutations when coming online after having called resumePausedMutations while offline', async () => {
1522+
const consoleMock = vi.spyOn(console, 'error')
1523+
consoleMock.mockImplementation(() => undefined)
1524+
onlineManager.setOnline(false)
1525+
1526+
const observer = new MutationObserver(queryClient, {
1527+
mutationFn: async () => 1,
1528+
})
1529+
1530+
void observer.mutate()
1531+
1532+
expect(observer.getCurrentResult().isPaused).toBeTruthy()
1533+
1534+
await queryClient.resumePausedMutations()
1535+
1536+
// still paused because we are still offline
1537+
expect(observer.getCurrentResult().isPaused).toBeTruthy()
1538+
1539+
onlineManager.setOnline(true)
1540+
1541+
await waitFor(() => {
1542+
expect(observer.getCurrentResult().status).toBe('success')
1543+
})
1544+
})
1545+
1546+
test('should resumePausedMutations when coming online after having restored cache (and resumed) while offline', async () => {
1547+
const consoleMock = vi.spyOn(console, 'error')
1548+
consoleMock.mockImplementation(() => undefined)
1549+
onlineManager.setOnline(false)
1550+
1551+
const observer = new MutationObserver(queryClient, {
1552+
mutationFn: async () => 1,
1553+
})
1554+
1555+
void observer.mutate()
1556+
1557+
expect(observer.getCurrentResult().isPaused).toBeTruthy()
1558+
1559+
const state = dehydrate(queryClient)
1560+
1561+
const newQueryClient = new QueryClient({
1562+
defaultOptions: {
1563+
mutations: {
1564+
mutationFn: async () => 1,
1565+
},
1566+
},
1567+
})
1568+
1569+
newQueryClient.mount()
1570+
1571+
hydrate(newQueryClient, state)
1572+
1573+
// still paused because we are still offline
1574+
expect(
1575+
newQueryClient.getMutationCache().getAll()[0]?.state.isPaused,
1576+
).toBeTruthy()
1577+
1578+
await newQueryClient.resumePausedMutations()
1579+
1580+
onlineManager.setOnline(true)
1581+
1582+
await waitFor(() => {
1583+
expect(
1584+
newQueryClient.getMutationCache().getAll()[0]?.state.status,
1585+
).toBe('success')
1586+
})
1587+
1588+
newQueryClient.unmount()
1589+
})
1590+
15241591
test('should notify queryCache and mutationCache after multiple mounts and single unmount', async () => {
15251592
const testClient = createQueryClient()
15261593
testClient.mount()

0 commit comments

Comments
 (0)