Skip to content

Commit 39a213e

Browse files
authored
fix(persistQueryClient): subscribe to both QueryCache and MutationCache
1 parent 49704c7 commit 39a213e

File tree

3 files changed

+110
-5
lines changed

3 files changed

+110
-5
lines changed

docs/src/pages/guides/mutations.md

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ useMutation(addTodo, {
195195
mutate(todo, {
196196
onSuccess: (data, error, variables, context) => {
197197
// Will execute only once, for the last mutation (Todo 3),
198-
// regardless which mutation resolves first
198+
// regardless which mutation resolves first
199199
},
200200
})
201201
})
@@ -279,6 +279,49 @@ hydrate(queryClient, state)
279279
queryClient.resumePausedMutations()
280280
```
281281

282+
### Persisting Offline mutations
283+
284+
If you persist offline mutations with the [persistQueryClient plugin](../plugins/persistQueryClient), mutations cannot be resumed when the page is reloaded unless you provide a default mutation function.
285+
286+
This is a technical limitation. When persisting to an external storage, only the state of mutations is persisted, as functions cannot be serialized. After hydration, the component that triggeres the mutation might not be mounted, so calling `resumePausedMutations` might yield an error: `No mutationFn found`.
287+
288+
```js
289+
const persister = createWebStoragePersister({
290+
storage: window.localStorage,
291+
})
292+
const queryClient = new QueryClient({
293+
defaultOptions: {
294+
queries: {
295+
cacheTime: 1000 * 60 * 60 * 24, // 24 hours
296+
},
297+
},
298+
})
299+
300+
// we need a default mutation function so that paused mutations can resume after a page reload
301+
queryClient.setMutationDefaults(['todos'], {
302+
mutationFn: ({ id, data }) => {
303+
return api.upateTodo(id, data)
304+
},
305+
})
306+
307+
export default function App() {
308+
return (
309+
<PersistQueryClientProvider
310+
client={queryClient}
311+
persistOptions={{ persister }}
312+
onSuccess={() => {
313+
// resume mutations after initial restore from localStorage was successful
314+
queryClient.resumePausedMutations()
315+
}}
316+
>
317+
<RestOfTheApp />
318+
</PersistQueryClientProvider>
319+
)
320+
}
321+
```
322+
323+
We also have an extensive [offline example](../examples/offline) that covers both queries and mutations.
324+
282325
## Further reading
283326

284327
For more information about mutations, have a look at [#12: Mastering Mutations in React Query](../community/tkdodos-blog#12-mastering-mutations-in-react-query) from

src/persistQueryClient/persist.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,15 +119,28 @@ export async function persistQueryClientSave({
119119
}
120120

121121
/**
122-
* Subscribe to QueryCache updates (for persisting)
122+
* Subscribe to QueryCache and MutationCache updates (for persisting)
123123
* @returns an unsubscribe function (to discontinue monitoring)
124124
*/
125125
export function persistQueryClientSubscribe(
126126
props: PersistedQueryClientSaveOptions
127127
) {
128-
return props.queryClient.getQueryCache().subscribe(() => {
129-
persistQueryClientSave(props)
130-
})
128+
const unsubscribeQueryCache = props.queryClient
129+
.getQueryCache()
130+
.subscribe(() => {
131+
persistQueryClientSave(props)
132+
})
133+
134+
const unusbscribeMutationCache = props.queryClient
135+
.getMutationCache()
136+
.subscribe(() => {
137+
persistQueryClientSave(props)
138+
})
139+
140+
return () => {
141+
unsubscribeQueryCache()
142+
unusbscribeMutationCache()
143+
}
131144
}
132145

133146
/**
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { createQueryClient } from '../../reactjs/tests/utils'
2+
import { sleep } from '../../core/utils'
3+
import {
4+
PersistedClient,
5+
Persister,
6+
persistQueryClientSubscribe,
7+
} from '../persist'
8+
9+
const createMockPersister = (): Persister => {
10+
let storedState: PersistedClient | undefined
11+
12+
return {
13+
async persistClient(persistClient: PersistedClient) {
14+
storedState = persistClient
15+
},
16+
async restoreClient() {
17+
await sleep(10)
18+
return storedState
19+
},
20+
removeClient() {
21+
storedState = undefined
22+
},
23+
}
24+
}
25+
26+
describe('persistQueryClientSubscribe', () => {
27+
test('should persist mutations', async () => {
28+
const queryClient = createQueryClient()
29+
30+
const persister = createMockPersister()
31+
32+
const unsubscribe = persistQueryClientSubscribe({
33+
queryClient,
34+
persister,
35+
dehydrateOptions: { shouldDehydrateMutation: () => true },
36+
})
37+
38+
queryClient.getMutationCache().build(queryClient, {
39+
mutationFn: async (text: string) => text,
40+
variables: 'todo',
41+
})
42+
43+
const result = await persister.restoreClient()
44+
45+
expect(result?.clientState.mutations).toHaveLength(1)
46+
47+
unsubscribe()
48+
})
49+
})

0 commit comments

Comments
 (0)