Skip to content

Commit eae7bde

Browse files
christina-de-martinezchristinamartinez
andauthored
docs: Replace accidentally-deleted content in mutations.md (#9254)
Co-authored-by: christinamartinez <[email protected]>
1 parent 2b9b122 commit eae7bde

File tree

1 file changed

+230
-1
lines changed

1 file changed

+230
-1
lines changed

docs/framework/react/guides/mutations.md

Lines changed: 230 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,4 +182,233 @@ useMutation({
182182

183183
[//]: # 'Example5'
184184

185-
You might find that you want to **trigger additional callbacks** beyond the ones defined on `useMutation` when calling `mutate`. This can be used to trigger component-specific side effects. To do that, you can provide any of the same callback options to the `
185+
You might find that you want to **trigger additional callbacks** beyond the ones defined on `useMutation` when calling `mutate`. This can be used to trigger component-specific side effects. To do that, you can provide any of the same callback options to the `mutate` function after your mutation variable. Supported options include: `onSuccess`, `onError` and `onSettled`. Please keep in mind that those additional callbacks won't run if your component unmounts _before_ the mutation finishes.Add commentMore actions
186+
187+
[//]: # 'Example6'
188+
189+
```tsx
190+
useMutation({
191+
mutationFn: addTodo,
192+
onSuccess: (data, variables, context) => {
193+
// I will fire first
194+
},
195+
onError: (error, variables, context) => {
196+
// I will fire first
197+
},
198+
onSettled: (data, error, variables, context) => {
199+
// I will fire first
200+
},
201+
})
202+
203+
mutate(todo, {
204+
onSuccess: (data, variables, context) => {
205+
// I will fire second!
206+
},
207+
onError: (error, variables, context) => {
208+
// I will fire second!
209+
},
210+
onSettled: (data, error, variables, context) => {
211+
// I will fire second!
212+
},
213+
})
214+
```
215+
216+
[//]: # 'Example6'
217+
218+
### Consecutive mutations
219+
220+
There is a slight difference in handling `onSuccess`, `onError` and `onSettled` callbacks when it comes to consecutive mutations. When passed to the `mutate` function, they will be fired up only _once_ and only if the component is still mounted. This is due to the fact that mutation observer is removed and resubscribed every time when the `mutate` function is called. On the contrary, `useMutation` handlers execute for each `mutate` call.
221+
222+
> Be aware that most likely, `mutationFn` passed to `useMutation` is asynchronous. In that case, the order in which mutations are fulfilled may differ from the order of `mutate` function calls.
223+
224+
[//]: # 'Example7'
225+
226+
```tsx
227+
useMutation({
228+
mutationFn: addTodo,
229+
onSuccess: (data, variables, context) => {
230+
// Will be called 3 times
231+
},
232+
})
233+
234+
const todos = ['Todo 1', 'Todo 2', 'Todo 3']
235+
todos.forEach((todo) => {
236+
mutate(todo, {
237+
onSuccess: (data, variables, context) => {
238+
// Will execute only once, for the last mutation (Todo 3),
239+
// regardless which mutation resolves first
240+
},
241+
})
242+
})
243+
```
244+
245+
[//]: # 'Example7'
246+
247+
## Promises
248+
249+
Use `mutateAsync` instead of `mutate` to get a promise which will resolve on success or throw on an error. This can for example be used to compose side effects.
250+
251+
[//]: # 'Example8'
252+
253+
```tsx
254+
const mutation = useMutation({ mutationFn: addTodo })
255+
256+
try {
257+
const todo = await mutation.mutateAsync(todo)
258+
console.log(todo)
259+
} catch (error) {
260+
console.error(error)
261+
} finally {
262+
console.log('done')
263+
}
264+
```
265+
266+
[//]: # 'Example8'
267+
268+
## Retry
269+
270+
By default, TanStack Query will not retry a mutation on error, but it is possible with the `retry` option:
271+
272+
[//]: # 'Example9'
273+
274+
```tsx
275+
const mutation = useMutation({
276+
mutationFn: addTodo,
277+
retry: 3,
278+
})
279+
```
280+
281+
[//]: # 'Example9'
282+
283+
If mutations fail because the device is offline, they will be retried in the same order when the device reconnects.
284+
285+
## Persist mutations
286+
287+
Mutations can be persisted to storage if needed and resumed at a later point. This can be done with the hydration functions:
288+
289+
[//]: # 'Example10'
290+
291+
```tsx
292+
const queryClient = new QueryClient()
293+
294+
// Define the "addTodo" mutation
295+
queryClient.setMutationDefaults(['addTodo'], {
296+
mutationFn: addTodo,
297+
onMutate: async (variables) => {
298+
// Cancel current queries for the todos list
299+
await queryClient.cancelQueries({ queryKey: ['todos'] })
300+
301+
// Create optimistic todo
302+
const optimisticTodo = { id: uuid(), title: variables.title }
303+
304+
// Add optimistic todo to todos list
305+
queryClient.setQueryData(['todos'], (old) => [...old, optimisticTodo])
306+
307+
// Return context with the optimistic todo
308+
return { optimisticTodo }
309+
},
310+
onSuccess: (result, variables, context) => {
311+
// Replace optimistic todo in the todos list with the result
312+
queryClient.setQueryData(['todos'], (old) =>
313+
old.map((todo) =>
314+
todo.id === context.optimisticTodo.id ? result : todo,
315+
),
316+
)
317+
},
318+
onError: (error, variables, context) => {
319+
// Remove optimistic todo from the todos list
320+
queryClient.setQueryData(['todos'], (old) =>
321+
old.filter((todo) => todo.id !== context.optimisticTodo.id),
322+
)
323+
},
324+
retry: 3,
325+
})
326+
327+
// Start mutation in some component:
328+
const mutation = useMutation({ mutationKey: ['addTodo'] })
329+
mutation.mutate({ title: 'title' })
330+
331+
// If the mutation has been paused because the device is for example offline,
332+
// Then the paused mutation can be dehydrated when the application quits:
333+
const state = dehydrate(queryClient)
334+
335+
// The mutation can then be hydrated again when the application is started:
336+
hydrate(queryClient, state)
337+
338+
// Resume the paused mutations:
339+
queryClient.resumePausedMutations()
340+
```
341+
342+
[//]: # 'Example10'
343+
344+
### Persisting Offline mutations
345+
346+
If you persist offline mutations with the [persistQueryClient plugin](../plugins/persistQueryClient.md), mutations cannot be resumed when the page is reloaded unless you provide a default mutation function.
347+
348+
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 triggers the mutation might not be mounted, so calling `resumePausedMutations` might yield an error: `No mutationFn found`.
349+
350+
[//]: # 'Example11'
351+
352+
```tsx
353+
const persister = createSyncStoragePersister({
354+
storage: window.localStorage,
355+
})
356+
const queryClient = new QueryClient({
357+
defaultOptions: {
358+
queries: {
359+
gcTime: 1000 * 60 * 60 * 24, // 24 hours
360+
},
361+
},
362+
})
363+
364+
// we need a default mutation function so that paused mutations can resume after a page reload
365+
queryClient.setMutationDefaults(['todos'], {
366+
mutationFn: ({ id, data }) => {
367+
return api.updateTodo(id, data)
368+
},
369+
})
370+
371+
export default function App() {
372+
return (
373+
<PersistQueryClientProvider
374+
client={queryClient}
375+
persistOptions={{ persister }}
376+
onSuccess={() => {
377+
// resume mutations after initial restore from localStorage was successful
378+
queryClient.resumePausedMutations()
379+
}}
380+
>
381+
<RestOfTheApp />
382+
</PersistQueryClientProvider>
383+
)
384+
}
385+
```
386+
387+
[//]: # 'Example11'
388+
389+
We also have an extensive [offline example](../examples/offline) that covers both queries and mutations.
390+
391+
## Mutation Scopes
392+
393+
Per default, all mutations run in parallel - even if you invoke `.mutate()` of the same mutation multiple times. Mutations can be given a `scope` with an `id` to avoid that. All mutations with the same `scope.id` will run in serial, which means when they are triggered, they will start in `isPaused: true` state if there is already a mutation for that scope in progress. They will be put into a queue and will automatically resume once their time in the queue has come.
394+
395+
[//]: # 'ExampleScopes'
396+
397+
```tsx
398+
const mutation = useMutation({
399+
mutationFn: addTodo,
400+
scope: {
401+
id: 'todo',
402+
},
403+
})
404+
```
405+
406+
[//]: # 'ExampleScopes'
407+
[//]: # 'Materials'
408+
409+
## Further reading
410+
411+
For more information about mutations, have a look at [#12: Mastering Mutations in React Query](../community/tkdodos-blog.md#12-mastering-mutations-in-react-query) from
412+
the Community Resources.
413+
414+
[//]: # 'Materials'

0 commit comments

Comments
 (0)