Skip to content

Commit 8da45d1

Browse files
feat: split mutate into mutate and mutateAsync (#1130)
* feat: split mutate into mutate and mutateAsync * docs: Update docs/src/pages/guides/mutations.md Co-authored-by: Tanner Linsley <[email protected]>
1 parent fee1376 commit 8da45d1

File tree

16 files changed

+342
-185
lines changed

16 files changed

+342
-185
lines changed

docs/src/pages/guides/invalidations-from-mutations.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Invalidating queries is only half the battle. Knowing **when** to invalidate the
88
For example, assume we have a mutation to post a new todo:
99

1010
```js
11-
const [mutate] = useMutation(postTodo)
11+
const mutation = useMutation(postTodo)
1212
```
1313

1414
When a successful `postTodo` mutation happens, we likely want all `todos` queries to get invalidated and possibly refetched to show the new todo item. To do this, you can use `useMutation`'s `onSuccess` options and the `client`'s `invalidateQueries` function:
@@ -19,7 +19,7 @@ import { useMutation, useQueryClient } from 'react-query'
1919
const client = useQueryClient()
2020

2121
// When this mutation succeeds, invalidate any queries with the `todos` or `reminders` query key
22-
const [mutate] = useMutation(addTodo, {
22+
const mutation = useMutation(addTodo, {
2323
onSuccess: () => {
2424
client.invalidateQueries('todos')
2525
client.invalidateQueries('reminders')

docs/src/pages/guides/mutations.md

Lines changed: 59 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,25 @@ Here's an example of a mutation that adds a new todo the server:
99

1010
```js
1111
function App() {
12-
const [
13-
mutate,
14-
{ isLoading, isError, isSuccess, data, error },
15-
] = useMutation(newTodo => axios.post('/todods', newTodo))
12+
const mutation = useMutation(newTodo => axios.post('/todods', newTodo))
1613

1714
return (
1815
<div>
19-
{isLoading ? (
16+
{mutation.isLoading ? (
2017
'Adding todo...'
2118
) : (
2219
<>
23-
{isError ? <div>An error occurred: {error.message}</div> : null}
20+
{mutation.isError ? (
21+
<div>An error occurred: {mutation.error.message}</div>
22+
) : null}
2423

25-
{isError ? <div>Todo added!</div> : null}
24+
{mutation.isError ? <div>Todo added!</div> : null}
2625

27-
<button onClick={mutate({ id: new Date(), title: 'Do Laundry' })}>
26+
<button
27+
onClick={() => {
28+
mutation.mutate({ id: new Date(), title: 'Do Laundry' })
29+
}}
30+
>
2831
Create Todo
2932
</button>
3033
</>
@@ -55,22 +58,22 @@ Even with just variables, mutations aren't all that special, but when used with
5558
```js
5659
// This will not work
5760
const CreateTodo = () => {
58-
const [mutate] = useMutation(event => {
61+
const mutation = useMutation(event => {
5962
event.preventDefault()
6063
return fetch('/api', new FormData(event.target))
6164
})
6265

63-
return <form onSubmit={mutate}>...</form>
66+
return <form onSubmit={mutation.mutate}>...</form>
6467
}
6568

6669
// This will work
6770
const CreateTodo = () => {
68-
const [mutate] = useMutation(formData => {
71+
const mutation = useMutation(formData => {
6972
return fetch('/api', formData)
7073
})
7174
const onSubmit = event => {
7275
event.preventDefault()
73-
mutate(new FormData(event.target))
76+
mutation.mutate(new FormData(event.target))
7477
}
7578

7679
return <form onSubmit={onSubmit}>...</form>
@@ -84,16 +87,18 @@ It's sometimes the case that you need to clear the `error` or `data` of a mutati
8487
```js
8588
const CreateTodo = () => {
8689
const [title, setTitle] = useState('')
87-
const [mutate, { error, reset }] = useMutation(createTodo)
90+
const mutation = useMutation(createTodo)
8891

89-
const onCreateTodo = async e => {
92+
const onCreateTodo = e => {
9093
e.preventDefault()
91-
await mutate({ title })
94+
mutation.mutate({ title })
9295
}
9396

9497
return (
9598
<form onSubmit={onCreateTodo}>
96-
{error && <h5 onClick={() => reset()}>{error}</h5>}
99+
{mutation.error && (
100+
<h5 onClick={() => mutation.reset()}>{mutation.error}</h5>
101+
)}
97102
<input
98103
type="text"
99104
value={title}
@@ -111,73 +116,88 @@ const CreateTodo = () => {
111116
`useMutation` comes with some helper options that allow quick and easy side-effects at any stage during the mutation lifecycle. These come in handy for both [invalidating and refetching queries after mutations](../invalidations-from-mutations) and even [optimistic updates](../optimistic-updates)
112117

113118
```js
114-
const [mutate] = useMutation(addTodo, {
115-
onMutate: (variables) => {
119+
useMutation(addTodo, {
120+
onMutate: variables => {
116121
// A mutation is about to happen!
117122

118-
// Optionally return a rollbackVariable
119-
return () => {
120-
// do some rollback logic
123+
// Optionally return a context object with a rollback function
124+
return {
125+
rollback: () => {
126+
// do some rollback logic
127+
},
121128
}
122-
}
123-
onError: (error, variables, rollbackVariable) => {
129+
},
130+
onError: (error, variables, context) => {
124131
// An error happened!
125-
if (rollbackVariable) rollbackVariable()
132+
if (context.rollback) {
133+
context.rollback()
134+
}
126135
},
127-
onSuccess: (data, variables, rollbackVariable) => {
136+
onSuccess: (data, variables, context) => {
128137
// Boom baby!
129138
},
130-
onSettled: (data, error, variables, rollbackVariable) => {
139+
onSettled: (data, error, variables, context) => {
131140
// Error or success... doesn't matter!
132141
},
133142
})
134143
```
135144

136-
The promise returned by `mutate()` can be helpful as well for performing more granular control flow in your app, and if you prefer that that promise only resolves **after** the `onSuccess` or `onSettled` callbacks, you can return a promise in either!:
145+
When returning a promise in any of the callback functions it will first be awaited before the next callback is called:
137146

138147
```js
139-
const [mutate] = useMutation(addTodo, {
148+
useMutation(addTodo, {
140149
onSuccess: async () => {
141150
console.log("I'm first!")
142151
},
143152
onSettled: async () => {
144153
console.log("I'm second!")
145154
},
146155
})
147-
148-
mutate(todo)
149156
```
150157

151158
You might find that you want to **add additional side-effects** to some of the `useMutation` lifecycle at the time of calling `mutate`. To do that, you can provide any of the same callback options to the `mutate` function after your mutation variable. Supported option overrides include:
152159

153160
- `onSuccess` - Will be fired after the `useMutation`-level `onSuccess` handler
154161
- `onError` - Will be fired after the `useMutation`-level `onError` handler
155162
- `onSettled` - Will be fired after the `useMutation`-level `onSettled` handler
156-
- `throwOnError` - Indicates that the `mutate` function should throw an error if one is encountered
157163

158164
```js
159-
const [mutate] = useMutation(addTodo, {
160-
onSuccess: (data, mutationVariables) => {
165+
useMutation(addTodo, {
166+
onSuccess: (data, variables, context) => {
161167
// I will fire first
162168
},
163-
onError: (error, mutationVariables) => {
169+
onError: (error, variables, context) => {
164170
// I will fire first
165171
},
166-
onSettled: (data, error, mutationVariables) => {
172+
onSettled: (data, error, variables, context) => {
167173
// I will fire first
168174
},
169175
})
170176

171177
mutate(todo, {
172-
onSuccess: (data, mutationVariables) => {
178+
onSuccess: (data, variables, context) => {
173179
// I will fire second!
174180
},
175-
onError: (error, mutationVariables) => {
181+
onError: (error, variables, context) => {
176182
// I will fire second!
177183
},
178-
onSettled: (data, error, mutationVariables) => {
184+
onSettled: (data, error, variables, context) => {
179185
// I will fire second!
180186
},
181-
throwOnError: true,
182187
})
183188
```
189+
190+
## Promises
191+
192+
Use `mutateAsync` instead of `mutate` to get a promise which will resolve on success or throw on an error:
193+
194+
```js
195+
const mutation = useMutation(addTodo)
196+
197+
try {
198+
const todo = await mutation.mutateAsync(todo)
199+
console.log(todo)
200+
} catch (error) {
201+
console.error(error)
202+
}
203+
```

docs/src/pages/guides/updates-from-mutation-responses.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ When dealing with mutations that **update** objects on the server, it's common f
88
```js
99
const client = useQueryClient()
1010

11-
const [mutate] = useMutation(editTodo, {
11+
const mutation = useMutation(editTodo, {
1212
onSuccess: data => client.setQueryData(['todo', { id: 5 }], data),
1313
})
1414

15-
mutate({
15+
mutation.mutate({
1616
id: 5,
1717
name: 'Do the laundry',
1818
})

docs/src/pages/quick-start.md

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,14 @@ This example very briefly illustrates the 3 core concepts of React Query:
1010
- Query Invalidation
1111

1212
```js
13-
import { useQuery, useMutation, useQueryClient, QueryCache, QueryClient, QueryClientProvider } from 'react-query'
13+
import {
14+
useQuery,
15+
useMutation,
16+
useQueryClient,
17+
QueryCache,
18+
QueryClient,
19+
QueryClientProvider,
20+
} from 'react-query'
1421
import { getTodos, postTodo } from '../my-api'
1522

1623
// Create a cache
@@ -25,18 +32,18 @@ function App() {
2532
<QueryClientProvider client={client}>
2633
<Todos />
2734
</QueryClientProvider>
28-
);
35+
)
2936
}
3037

3138
function Todos() {
3239
// Access the client
3340
const client = useQueryClient()
3441

3542
// Queries
36-
const todosQuery = useQuery('todos', getTodos)
43+
const query = useQuery('todos', getTodos)
3744

3845
// Mutations
39-
const [addTodo] = useMutation(postTodo, {
46+
const mutation = useMutation(postTodo, {
4047
onSuccess: () => {
4148
// Invalidate and refetch
4249
client.invalidateQueries('todos')
@@ -46,26 +53,26 @@ function Todos() {
4653
return (
4754
<div>
4855
<ul>
49-
{todosQuery.data.map(todo => (
56+
{query.data.map(todo => (
5057
<li key={todo.id}>{todo.title}</li>
5158
))}
5259
</ul>
5360

5461
<button
55-
onClick={() =>
56-
addTodo({
62+
onClick={() => {
63+
mutation.mutate({
5764
id: Date.now(),
58-
title: 'Do Laundry'
65+
title: 'Do Laundry',
5966
})
60-
}
67+
}}
6168
>
6269
Add Todo
6370
</button>
6471
</div>
6572
)
6673
}
6774

68-
render(<App />, document.getElementById('root'));
75+
render(<App />, document.getElementById('root'))
6976
```
7077

7178
These three concepts make up most of the core functionality of React Query. The next sections of the documentation will go over each of these core concepts in great detail.

docs/src/pages/reference/useMutation.md

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,29 @@ title: useMutation
44
---
55

66
```js
7-
const [
7+
const {
8+
data,
9+
error,
10+
isError,
11+
isIdle,
12+
isLoading,
13+
isSuccess,
814
mutate,
9-
{ status, isIdle, isLoading, isSuccess, isError, data, error, reset },
10-
] = useMutation(mutationFn, {
11-
onMutate,
12-
onSuccess,
15+
mutateAsync,
16+
reset,
17+
status,
18+
} = useMutation(mutationFn, {
1319
onError,
20+
onMutate,
1421
onSettled,
15-
throwOnError,
22+
onSuccess,
1623
useErrorBoundary,
1724
})
1825

19-
const promise = mutate(variables, {
20-
onSuccess,
21-
onSettled,
26+
mutate(variables, {
2227
onError,
23-
throwOnError,
28+
onSettled,
29+
onSuccess,
2430
})
2531
```
2632

@@ -50,22 +56,21 @@ const promise = mutate(variables, {
5056
- This function will fire when the mutation is either successfully fetched or encounters an error and be passed either the data or error
5157
- Fires after the `mutate`-level `onSettled` handler (if it is defined)
5258
- If a promise is returned, it will be awaited and resolved before proceeding
53-
- `throwOnError`
54-
- Defaults to `false`
55-
- Set this to `true` if failed mutations should re-throw errors from the mutation function to the `mutate` function.
5659
- `useErrorBoundary`
5760
- Defaults to the global query config's `useErrorBoundary` value, which is `false`
5861
- Set this to true if you want mutation errors to be thrown in the render phase and propagate to the nearest error boundary
5962

6063
**Returns**
6164

62-
- `mutate: (variables: TVariables, { onSuccess, onSettled, onError, throwOnError }) => Promise<TData>`
65+
- `mutate: (variables: TVariables, { onSuccess, onSettled, onError }) => void`
6366
- The mutation function you can call with variables to trigger the mutation and optionally override the original mutation options.
6467
- `variables: TVariables`
6568
- Optional
6669
- The variables object to pass to the `mutationFn`.
6770
- Remaining options extend the same options described above in the `useMutation` hook.
6871
- Lifecycle callbacks defined here will fire **after** those of the same type defined in the `useMutation`-level options.
72+
- `mutateAsync: (variables: TVariables, { onSuccess, onSettled, onError }) => Promise<TData>`
73+
- Similar to `mutate` but returns a promise which can be awaited.
6974
- `status: string`
7075
- Will be:
7176
- `idle` initial status prior to the mutation function executing.

0 commit comments

Comments
 (0)