0
# Mutations
1
2
Data mutation operations with optimistic updates, automatic error handling, and cache invalidation patterns. The `useMutation` hook handles side effects like creating, updating, or deleting data.
3
4
## Capabilities
5
6
### useMutation Hook
7
8
The main hook for performing data mutations with loading states and error handling.
9
10
```typescript { .api }
11
/**
12
* Create a mutation for performing side effects
13
* @param options - Mutation configuration options
14
* @returns Mutation result with mutate functions and states
15
*/
16
function useMutation<TData = unknown, TError = unknown, TVariables = void, TContext = unknown>(
17
options: UseMutationOptions<TData, TError, TVariables, TContext>
18
): UseMutationResult<TData, TError, TVariables, TContext>;
19
20
/**
21
* Create a mutation with separate mutationFn parameter
22
* @param mutationFn - Function that performs the mutation
23
* @param options - Additional mutation configuration
24
* @returns Mutation result with mutate functions and states
25
*/
26
function useMutation<TData = unknown, TError = unknown, TVariables = void, TContext = unknown>(
27
mutationFn: MutationFunction<TData, TVariables>,
28
options?: Omit<UseMutationOptions<TData, TError, TVariables, TContext>, 'mutationFn'>
29
): UseMutationResult<TData, TError, TVariables, TContext>;
30
31
/**
32
* Create a mutation with mutation key for tracking and deduplication
33
* @param mutationKey - Unique identifier for the mutation
34
* @param options - Mutation configuration
35
* @returns Mutation result with mutate functions and states
36
*/
37
function useMutation<TData = unknown, TError = unknown, TVariables = void, TContext = unknown>(
38
mutationKey: MutationKey,
39
mutationFn: MutationFunction<TData, TVariables>,
40
options?: Omit<UseMutationOptions<TData, TError, TVariables, TContext>, 'mutationKey' | 'mutationFn'>
41
): UseMutationResult<TData, TError, TVariables, TContext>;
42
```
43
44
**Usage Examples:**
45
46
```typescript
47
import { useMutation, useQueryClient } from "react-query";
48
49
// Basic mutation
50
const createPost = useMutation({
51
mutationFn: (newPost) => fetch('/api/posts', {
52
method: 'POST',
53
body: JSON.stringify(newPost)
54
}).then(res => res.json()),
55
onSuccess: () => {
56
alert('Post created!');
57
}
58
});
59
60
// Mutation with cache invalidation
61
const updateUser = useMutation({
62
mutationFn: ({ id, data }) => fetch(`/api/users/${id}`, {
63
method: 'PUT',
64
body: JSON.stringify(data)
65
}),
66
onSuccess: (data, variables) => {
67
queryClient.invalidateQueries({ queryKey: ['user', variables.id] });
68
}
69
});
70
71
// Optimistic mutation
72
const toggleTodo = useMutation({
73
mutationFn: ({ id, completed }) =>
74
fetch(`/api/todos/${id}`, {
75
method: 'PATCH',
76
body: JSON.stringify({ completed })
77
}),
78
onMutate: async (variables) => {
79
// Cancel outgoing refetches
80
await queryClient.cancelQueries({ queryKey: ['todos'] });
81
82
// Snapshot previous value
83
const previousTodos = queryClient.getQueryData(['todos']);
84
85
// Optimistically update
86
queryClient.setQueryData(['todos'], (old) =>
87
old.map(todo =>
88
todo.id === variables.id
89
? { ...todo, completed: variables.completed }
90
: todo
91
)
92
);
93
94
return { previousTodos };
95
},
96
onError: (err, variables, context) => {
97
// Rollback on error
98
queryClient.setQueryData(['todos'], context.previousTodos);
99
},
100
onSettled: () => {
101
// Always refetch after error or success
102
queryClient.invalidateQueries({ queryKey: ['todos'] });
103
}
104
});
105
106
// Using the mutations
107
const handleCreatePost = () => {
108
createPost.mutate({ title: 'New Post', content: 'Content' });
109
};
110
111
const handleUpdateAsync = async () => {
112
try {
113
const result = await updateUser.mutateAsync({
114
id: 1,
115
data: { name: 'Updated Name' }
116
});
117
console.log('Update successful:', result);
118
} catch (error) {
119
console.error('Update failed:', error);
120
}
121
};
122
```
123
124
### Mutation Result Interface
125
126
The return value from `useMutation` containing mutation functions and states.
127
128
```typescript { .api }
129
interface UseMutationResult<TData = unknown, TError = unknown, TVariables = unknown, TContext = unknown> {
130
/** Function to trigger the mutation (fire-and-forget) */
131
mutate: UseMutateFunction<TData, TError, TVariables, TContext>;
132
/** Async function to trigger mutation and return promise */
133
mutateAsync: UseMutateAsyncFunction<TData, TError, TVariables, TContext>;
134
/** Data returned from successful mutation */
135
data: TData | undefined;
136
/** Error object if mutation failed */
137
error: TError | null;
138
/** True if mutation is in error state */
139
isError: boolean;
140
/** True if mutation is currently running */
141
isPending: boolean;
142
/** True if mutation succeeded */
143
isSuccess: boolean;
144
/** True if mutation has been triggered at least once */
145
isIdle: boolean;
146
/** Current status of the mutation */
147
status: 'idle' | 'pending' | 'error' | 'success';
148
/** Variables passed to the last mutation call */
149
variables: TVariables | undefined;
150
/** Context returned from onMutate */
151
context: TContext | undefined;
152
/** Number of times mutation has failed */
153
failureCount: number;
154
/** Reason mutation is paused */
155
failureReason: TError | null;
156
/** True if mutation is paused */
157
isPaused: boolean;
158
/** Function to reset mutation state */
159
reset: () => void;
160
}
161
162
type UseMutateFunction<TData = unknown, TError = unknown, TVariables = void, TContext = unknown> = (
163
variables: TVariables,
164
options?: MutateOptions<TData, TError, TVariables, TContext>
165
) => void;
166
167
type UseMutateAsyncFunction<TData = unknown, TError = unknown, TVariables = void, TContext = unknown> = (
168
variables: TVariables,
169
options?: MutateOptions<TData, TError, TVariables, TContext>
170
) => Promise<TData>;
171
```
172
173
### Mutation Options Interface
174
175
Configuration options for `useMutation` hook.
176
177
```typescript { .api }
178
interface UseMutationOptions<TData = unknown, TError = unknown, TVariables = void, TContext = unknown> {
179
/** Unique identifier for the mutation */
180
mutationKey?: MutationKey;
181
/** Function that performs the mutation */
182
mutationFn?: MutationFunction<TData, TVariables>;
183
/** Retry configuration */
184
retry?: boolean | number | ((failureCount: number, error: TError) => boolean);
185
/** Delay between retries */
186
retryDelay?: number | ((retryAttempt: number, error: TError) => number);
187
/** Callback before mutation starts (for optimistic updates) */
188
onMutate?: (variables: TVariables) => Promise<TContext> | TContext | void;
189
/** Callback on successful mutation */
190
onSuccess?: (data: TData, variables: TVariables, context: TContext | undefined) => Promise<unknown> | unknown;
191
/** Callback on mutation error */
192
onError?: (error: TError, variables: TVariables, context: TContext | undefined) => Promise<unknown> | unknown;
193
/** Callback after mutation settles (success or error) */
194
onSettled?: (data: TData | undefined, error: TError | null, variables: TVariables, context: TContext | undefined) => Promise<unknown> | unknown;
195
/** React context for QueryClient */
196
context?: React.Context<QueryClient | undefined>;
197
/** Whether to throw errors to error boundaries */
198
useErrorBoundary?: boolean | ((error: TError, variables: TVariables, context: TContext | undefined) => boolean);
199
/** Additional metadata */
200
meta?: MutationMeta;
201
/** Network mode configuration */
202
networkMode?: 'online' | 'always' | 'offlineFirst';
203
}
204
205
interface MutateOptions<TData = unknown, TError = unknown, TVariables = void, TContext = unknown> {
206
/** Override onSuccess for this call */
207
onSuccess?: (data: TData, variables: TVariables, context: TContext | undefined) => Promise<unknown> | unknown;
208
/** Override onError for this call */
209
onError?: (error: TError, variables: TVariables, context: TContext | undefined) => Promise<unknown> | unknown;
210
/** Override onSettled for this call */
211
onSettled?: (data: TData | undefined, error: TError | null, variables: TVariables, context: TContext | undefined) => Promise<unknown> | unknown;
212
}
213
```
214
215
### Mutation Function Interface
216
217
The function that actually performs the mutation.
218
219
```typescript { .api }
220
type MutationFunction<TData = unknown, TVariables = void> = (
221
variables: TVariables
222
) => Promise<TData>;
223
224
type MutationKey = readonly unknown[];
225
226
type MutationMeta = Record<string, unknown>;
227
```
228
229
## Advanced Usage Patterns
230
231
### Cache Updates
232
233
Different strategies for updating cached data after mutations:
234
235
```typescript
236
const queryClient = useQueryClient();
237
238
// 1. Invalidation (refetch)
239
const mutation = useMutation({
240
mutationFn: updateUser,
241
onSuccess: (data, variables) => {
242
// Invalidate and refetch
243
queryClient.invalidateQueries({ queryKey: ['user', variables.id] });
244
queryClient.invalidateQueries({ queryKey: ['users'] });
245
}
246
});
247
248
// 2. Direct cache update
249
const mutation = useMutation({
250
mutationFn: updateUser,
251
onSuccess: (updatedUser, variables) => {
252
// Update specific user query
253
queryClient.setQueryData(['user', variables.id], updatedUser);
254
255
// Update users list
256
queryClient.setQueryData(['users'], (oldUsers) =>
257
oldUsers.map(user =>
258
user.id === variables.id ? updatedUser : user
259
)
260
);
261
}
262
});
263
264
// 3. Add to cache (for create operations)
265
const createMutation = useMutation({
266
mutationFn: createUser,
267
onSuccess: (newUser) => {
268
// Add to users list
269
queryClient.setQueryData(['users'], (oldUsers) => [
270
...oldUsers,
271
newUser
272
]);
273
274
// Set individual user query
275
queryClient.setQueryData(['user', newUser.id], newUser);
276
}
277
});
278
```
279
280
### Global Mutation States
281
282
Monitor all mutations using `useIsMutating`:
283
284
```typescript
285
import { useIsMutating } from "react-query";
286
287
function GlobalLoadingIndicator() {
288
const isMutating = useIsMutating();
289
290
if (isMutating) {
291
return <div>Saving changes...</div>;
292
}
293
294
return null;
295
}
296
297
// Filter specific mutations
298
function UserUpdateIndicator({ userId }: { userId: string }) {
299
const isUpdatingUser = useIsMutating({
300
mutationKey: ['updateUser', userId]
301
});
302
303
return isUpdatingUser ? <div>Updating user...</div> : null;
304
}
305
```
306
307
### Error Recovery
308
309
```typescript
310
const mutation = useMutation({
311
mutationFn: updateData,
312
retry: (failureCount, error) => {
313
// Don't retry client errors (4xx)
314
if (error.status >= 400 && error.status < 500) {
315
return false;
316
}
317
318
// Retry server errors up to 3 times
319
return failureCount < 3;
320
},
321
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
322
onError: (error, variables, context) => {
323
// Handle error
324
toast.error(`Failed to update: ${error.message}`);
325
326
// Log for analytics
327
analytics.track('mutation_error', {
328
mutation: 'updateData',
329
error: error.message,
330
variables
331
});
332
}
333
});
334
335
// Manual retry
336
if (mutation.isError) {
337
return (
338
<div>
339
<p>Update failed: {mutation.error.message}</p>
340
<button onClick={() => mutation.reset()}>
341
Dismiss
342
</button>
343
<button onClick={() => mutation.mutate(lastVariables)}>
344
Retry
345
</button>
346
</div>
347
);
348
}
349
```
350
351
### Sequential Mutations
352
353
Chain mutations together:
354
355
```typescript
356
function useSequentialMutations() {
357
const queryClient = useQueryClient();
358
359
const createUser = useMutation({
360
mutationFn: (userData) => api.createUser(userData)
361
});
362
363
const assignRole = useMutation({
364
mutationFn: ({ userId, roleId }) => api.assignRole(userId, roleId)
365
});
366
367
const sendWelcomeEmail = useMutation({
368
mutationFn: (userId) => api.sendWelcomeEmail(userId)
369
});
370
371
const createUserWithRole = async (userData, roleId) => {
372
try {
373
const user = await createUser.mutateAsync(userData);
374
await assignRole.mutateAsync({ userId: user.id, roleId });
375
await sendWelcomeEmail.mutateAsync(user.id);
376
377
// Refresh users list
378
queryClient.invalidateQueries({ queryKey: ['users'] });
379
380
return user;
381
} catch (error) {
382
// Handle any step failure
383
console.error('User creation process failed:', error);
384
throw error;
385
}
386
};
387
388
return {
389
createUserWithRole,
390
isLoading: createUser.isPending || assignRole.isPending || sendWelcomeEmail.isPending
391
};
392
}
393
```