0
# Mutation Hooks
1
2
React hooks for data modifications with optimistic updates, error handling, and automatic cache invalidation. These hooks are automatically generated for each mutation procedure in your tRPC router.
3
4
## Capabilities
5
6
### useMutation
7
8
Primary hook for data mutations with comprehensive state management and lifecycle callbacks.
9
10
```typescript { .api }
11
/**
12
* Hook for performing data mutations through tRPC mutation procedures
13
* @param opts - Mutation configuration options including callbacks and settings
14
* @returns Mutation result with mutate functions, loading states, and error information
15
*/
16
procedure.useMutation<TContext = unknown>(
17
opts?: UseTRPCMutationOptions<TInput, TError, TOutput, TContext>
18
): UseTRPCMutationResult<TOutput, TError, TInput, TContext>;
19
20
interface UseTRPCMutationOptions<TInput, TError, TOutput, TContext = unknown>
21
extends Omit<UseMutationOptions<TOutput, TError, TInput, TContext>, 'mutationFn'> {
22
trpc?: TRPCReactRequestOptions;
23
}
24
25
interface UseTRPCMutationResult<TOutput, TError, TInput, TContext>
26
extends UseMutationResult<TOutput, TError, TInput, TContext> {
27
trpc: TRPCHookResult;
28
}
29
```
30
31
**Usage Examples:**
32
33
```typescript
34
import { trpc } from "./utils/trpc";
35
36
function CreateUserForm() {
37
const utils = trpc.useUtils();
38
39
const createUser = trpc.user.create.useMutation({
40
onSuccess: (newUser) => {
41
console.log("User created:", newUser);
42
// Invalidate and refetch user list
43
utils.user.list.invalidate();
44
},
45
onError: (error) => {
46
console.error("Failed to create user:", error.message);
47
},
48
});
49
50
const handleSubmit = (formData: { name: string; email: string }) => {
51
createUser.mutate(formData);
52
};
53
54
return (
55
<form onSubmit={(e) => {
56
e.preventDefault();
57
const formData = new FormData(e.currentTarget);
58
handleSubmit({
59
name: formData.get('name') as string,
60
email: formData.get('email') as string,
61
});
62
}}>
63
<input name="name" placeholder="Name" required />
64
<input name="email" type="email" placeholder="Email" required />
65
<button
66
type="submit"
67
disabled={createUser.isPending}
68
>
69
{createUser.isPending ? "Creating..." : "Create User"}
70
</button>
71
{createUser.error && (
72
<div>Error: {createUser.error.message}</div>
73
)}
74
</form>
75
);
76
}
77
```
78
79
### Optimistic Updates
80
81
Implement optimistic updates to improve user experience by updating the UI immediately before the server responds.
82
83
```typescript { .api }
84
// Optimistic update pattern using onMutate callback
85
interface OptimisticUpdateOptions<TInput, TOutput, TContext> {
86
onMutate?: (variables: TInput) => Promise<TContext> | TContext;
87
onError?: (error: TError, variables: TInput, context: TContext | undefined) => void;
88
onSettled?: (data: TOutput | undefined, error: TError | null, variables: TInput, context: TContext | undefined) => void;
89
}
90
```
91
92
**Usage Examples:**
93
94
```typescript
95
function OptimisticUserUpdate({ userId }: { userId: number }) {
96
const utils = trpc.useUtils();
97
98
const updateUser = trpc.user.update.useMutation({
99
onMutate: async (updateData) => {
100
// Cancel outgoing refetches
101
await utils.user.get.cancel({ id: userId });
102
103
// Get current data
104
const previousUser = utils.user.get.getData({ id: userId });
105
106
// Optimistically update the cache
107
utils.user.get.setData(
108
{ id: userId },
109
(old) => old ? { ...old, ...updateData } : undefined
110
);
111
112
// Return context with previous data for rollback
113
return { previousUser };
114
},
115
onError: (error, variables, context) => {
116
// Rollback on error
117
if (context?.previousUser) {
118
utils.user.get.setData({ id: userId }, context.previousUser);
119
}
120
},
121
onSettled: () => {
122
// Refetch to ensure consistency
123
utils.user.get.invalidate({ id: userId });
124
},
125
});
126
127
const handleUpdate = (data: { name: string }) => {
128
updateUser.mutate({ id: userId, ...data });
129
};
130
131
return (
132
<button onClick={() => handleUpdate({ name: "New Name" })}>
133
Update User
134
</button>
135
);
136
}
137
```
138
139
### Mutation State Management
140
141
Access comprehensive mutation state information for UI feedback.
142
143
```typescript { .api }
144
interface UseTRPCMutationResult<TOutput, TError, TInput, TContext> {
145
// Data and state
146
data: TOutput | undefined;
147
error: TError | null;
148
isPending: boolean;
149
isIdle: boolean;
150
isSuccess: boolean;
151
isError: boolean;
152
153
// Mutation functions
154
mutate: (variables: TInput, options?: MutateOptions<TOutput, TError, TInput, TContext>) => void;
155
mutateAsync: (variables: TInput, options?: MutateOptions<TOutput, TError, TInput, TContext>) => Promise<TOutput>;
156
157
// Reset function
158
reset: () => void;
159
160
// Status information
161
status: 'idle' | 'pending' | 'error' | 'success';
162
submittedAt: number;
163
164
// tRPC specific
165
trpc: TRPCHookResult;
166
}
167
```
168
169
**Usage Examples:**
170
171
```typescript
172
function MutationStatusExample() {
173
const deleteUser = trpc.user.delete.useMutation();
174
175
const handleDelete = async (userId: number) => {
176
try {
177
await deleteUser.mutateAsync({ id: userId });
178
alert("User deleted successfully");
179
} catch (error) {
180
console.error("Delete failed:", error);
181
}
182
};
183
184
return (
185
<div>
186
<button
187
onClick={() => handleDelete(1)}
188
disabled={deleteUser.isPending}
189
>
190
Delete User
191
</button>
192
193
{/* Status indicators */}
194
{deleteUser.isPending && <div>Deleting...</div>}
195
{deleteUser.isError && (
196
<div>Error: {deleteUser.error?.message}</div>
197
)}
198
{deleteUser.isSuccess && (
199
<div>User deleted successfully!</div>
200
)}
201
202
{/* Reset mutation state */}
203
{(deleteUser.isError || deleteUser.isSuccess) && (
204
<button onClick={() => deleteUser.reset()}>
205
Reset
206
</button>
207
)}
208
</div>
209
);
210
}
211
```
212
213
### Batch Mutations
214
215
Handle multiple related mutations with proper error handling and state management.
216
217
```typescript
218
function BatchMutationExample() {
219
const utils = trpc.useUtils();
220
const createUser = trpc.user.create.useMutation();
221
const updateSettings = trpc.settings.update.useMutation();
222
223
const handleBatchOperation = async () => {
224
try {
225
// Perform mutations sequentially
226
const newUser = await createUser.mutateAsync({
227
name: "John Doe",
228
email: "john@example.com",
229
});
230
231
await updateSettings.mutateAsync({
232
userId: newUser.id,
233
theme: "dark",
234
});
235
236
// Invalidate related queries
237
utils.user.list.invalidate();
238
utils.settings.get.invalidate();
239
240
alert("Batch operation completed successfully");
241
} catch (error) {
242
console.error("Batch operation failed:", error);
243
}
244
};
245
246
const isLoading = createUser.isPending || updateSettings.isPending;
247
248
return (
249
<button onClick={handleBatchOperation} disabled={isLoading}>
250
{isLoading ? "Processing..." : "Create User & Settings"}
251
</button>
252
);
253
}
254
```
255
256
### File Upload Mutations
257
258
Handle file uploads and form data through tRPC mutations.
259
260
```typescript
261
function FileUploadExample() {
262
const uploadAvatar = trpc.user.uploadAvatar.useMutation({
263
onSuccess: (result) => {
264
console.log("Upload successful:", result.url);
265
},
266
onError: (error) => {
267
console.error("Upload failed:", error.message);
268
},
269
});
270
271
const handleFileUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
272
const file = event.target.files?.[0];
273
if (!file) return;
274
275
const formData = new FormData();
276
formData.append('file', file);
277
formData.append('userId', '123');
278
279
uploadAvatar.mutate(formData);
280
};
281
282
return (
283
<div>
284
<input
285
type="file"
286
onChange={handleFileUpload}
287
accept="image/*"
288
disabled={uploadAvatar.isPending}
289
/>
290
{uploadAvatar.isPending && <div>Uploading...</div>}
291
{uploadAvatar.data && (
292
<img src={uploadAvatar.data.url} alt="Uploaded avatar" />
293
)}
294
</div>
295
);
296
}
297
```
298
299
### Global Mutation Settings
300
301
Configure global mutation behavior through the mutation overrides system.
302
303
```typescript
304
// In your tRPC setup
305
const trpc = createTRPCReact<AppRouter>({
306
overrides: {
307
useMutation: {
308
onSuccess: ({ originalFn }) => {
309
// Global success handler
310
console.log("Mutation succeeded globally");
311
312
// Call the original success handler
313
originalFn();
314
315
// Add global success behavior
316
showSuccessToast("Operation completed successfully");
317
},
318
},
319
},
320
});
321
322
function showSuccessToast(message: string) {
323
// Your toast implementation
324
console.log("Toast:", message);
325
}
326
```
327
328
## Common Patterns
329
330
### Loading States
331
332
```typescript
333
function LoadingStateExample() {
334
const mutation = trpc.user.create.useMutation();
335
336
return (
337
<button
338
onClick={() => mutation.mutate({ name: "Test" })}
339
disabled={mutation.isPending}
340
>
341
{mutation.isPending ? (
342
<>
343
<Spinner /> Creating...
344
</>
345
) : (
346
"Create User"
347
)}
348
</button>
349
);
350
}
351
```
352
353
### Error Boundaries
354
355
```typescript
356
function MutationWithErrorBoundary() {
357
const mutation = trpc.user.create.useMutation({
358
onError: (error) => {
359
// Log error for debugging
360
console.error("Mutation error:", error);
361
362
// Report to error tracking service
363
errorService.report(error);
364
},
365
});
366
367
return (
368
<button onClick={() => mutation.mutate({ name: "Test" })}>
369
Create User
370
</button>
371
);
372
}
373
```
374
375
### Cache Invalidation Patterns
376
377
```typescript
378
function CacheInvalidationExample() {
379
const utils = trpc.useUtils();
380
381
const createPost = trpc.post.create.useMutation({
382
onSuccess: () => {
383
// Invalidate all post queries
384
utils.post.invalidate();
385
386
// Invalidate specific user's posts
387
utils.post.byUser.invalidate({ userId: currentUserId });
388
389
// Refetch specific query
390
utils.post.list.refetch();
391
},
392
});
393
394
return (
395
<button onClick={() => createPost.mutate({ title: "New Post" })}>
396
Create Post
397
</button>
398
);
399
}