0
# Data Mutations
1
2
Composables for creating, updating, and deleting data with optimistic updates, automatic query invalidation, and reactive state management.
3
4
## Capabilities
5
6
### useMutation
7
8
Main composable for data mutations with automatic error handling and query invalidation.
9
10
```typescript { .api }
11
/**
12
* Main composable for data mutations with optimistic updates
13
* @param options - Mutation configuration options with Vue reactivity support
14
* @param queryClient - Optional query client instance
15
* @returns Reactive mutation state and execution functions
16
*/
17
function useMutation<TData, TError, TVariables, TContext>(
18
options: UseMutationOptions<TData, TError, TVariables, TContext>,
19
queryClient?: QueryClient
20
): UseMutationReturnType<TData, TError, TVariables, TContext>;
21
22
interface UseMutationOptions<TData, TError, TVariables, TContext> {
23
mutationFn?: MaybeRefOrGetter<
24
(variables: TVariables) => Promise<TData> | TData
25
>;
26
mutationKey?: MaybeRefOrGetter<MutationKey>;
27
onMutate?: MaybeRefOrGetter<
28
(variables: TVariables) => Promise<TContext | void> | TContext | void
29
>;
30
onSuccess?: MaybeRefOrGetter<
31
(data: TData, variables: TVariables, context: TContext | undefined) => Promise<unknown> | unknown
32
>;
33
onError?: MaybeRefOrGetter<
34
(error: TError, variables: TVariables, context: TContext | undefined) => Promise<unknown> | unknown
35
>;
36
onSettled?: MaybeRefOrGetter<
37
(data: TData | undefined, error: TError | null, variables: TVariables, context: TContext | undefined) => Promise<unknown> | unknown
38
>;
39
retry?: MaybeRefOrGetter<boolean | number | ((failureCount: number, error: TError) => boolean)>;
40
retryDelay?: MaybeRefOrGetter<number | ((retryAttempt: number, error: TError) => number)>;
41
throwOnError?: MaybeRefOrGetter<boolean | ((error: TError) => boolean)>;
42
meta?: MaybeRefOrGetter<MutationMeta>;
43
shallow?: boolean;
44
}
45
46
interface UseMutationReturnType<TData, TError, TVariables, TContext> {
47
data: Ref<TData | undefined>;
48
error: Ref<TError | null>;
49
failureCount: Ref<number>;
50
failureReason: Ref<TError | null>;
51
isError: Ref<boolean>;
52
isIdle: Ref<boolean>;
53
isPending: Ref<boolean>;
54
isPaused: Ref<boolean>;
55
isSuccess: Ref<boolean>;
56
mutate: (variables: TVariables, options?: MutateOptions<TData, TError, TVariables, TContext>) => void;
57
mutateAsync: (variables: TVariables, options?: MutateOptions<TData, TError, TVariables, TContext>) => Promise<TData>;
58
reset: () => void;
59
status: Ref<MutationStatus>;
60
submittedAt: Ref<number>;
61
variables: Ref<TVariables | undefined>;
62
}
63
64
interface MutateOptions<TData, TError, TVariables, TContext> {
65
onSuccess?: (data: TData, variables: TVariables, context: TContext | undefined) => Promise<unknown> | unknown;
66
onError?: (error: TError, variables: TVariables, context: TContext | undefined) => Promise<unknown> | unknown;
67
onSettled?: (data: TData | undefined, error: TError | null, variables: TVariables, context: TContext | undefined) => Promise<unknown> | unknown;
68
}
69
```
70
71
**Usage Examples:**
72
73
```typescript
74
import { useMutation, useQueryClient } from '@tanstack/vue-query';
75
76
// Basic mutation
77
const { mutate, isPending, error } = useMutation({
78
mutationFn: (newPost) =>
79
fetch('/api/posts', {
80
method: 'POST',
81
headers: { 'Content-Type': 'application/json' },
82
body: JSON.stringify(newPost)
83
}).then(res => res.json()),
84
onSuccess: () => {
85
console.log('Post created successfully!');
86
},
87
onError: (error) => {
88
console.error('Failed to create post:', error);
89
}
90
});
91
92
// Trigger mutation
93
const createPost = () => {
94
mutate({
95
title: 'New Post',
96
content: 'This is the content'
97
});
98
};
99
100
// Mutation with query invalidation
101
const queryClient = useQueryClient();
102
const { mutate: updateUser } = useMutation({
103
mutationFn: ({ id, data }) =>
104
fetch(`/api/users/${id}`, {
105
method: 'PUT',
106
headers: { 'Content-Type': 'application/json' },
107
body: JSON.stringify(data)
108
}).then(res => res.json()),
109
onSuccess: (data, variables) => {
110
// Invalidate and refetch user queries
111
queryClient.invalidateQueries({ queryKey: ['users'] });
112
queryClient.invalidateQueries({ queryKey: ['user', variables.id] });
113
}
114
});
115
116
// Optimistic updates
117
const { mutate: toggleTodo } = useMutation({
118
mutationFn: ({ id, completed }) =>
119
fetch(`/api/todos/${id}`, {
120
method: 'PATCH',
121
headers: { 'Content-Type': 'application/json' },
122
body: JSON.stringify({ completed })
123
}).then(res => res.json()),
124
onMutate: async ({ id, completed }) => {
125
// Cancel outgoing refetches
126
await queryClient.cancelQueries({ queryKey: ['todos'] });
127
128
// Snapshot previous value
129
const previousTodos = queryClient.getQueryData(['todos']);
130
131
// Optimistically update
132
queryClient.setQueryData(['todos'], (old) =>
133
old?.map(todo =>
134
todo.id === id ? { ...todo, completed } : todo
135
)
136
);
137
138
// Return context for rollback
139
return { previousTodos };
140
},
141
onError: (err, variables, context) => {
142
// Rollback on error
143
if (context?.previousTodos) {
144
queryClient.setQueryData(['todos'], context.previousTodos);
145
}
146
},
147
onSettled: () => {
148
// Always refetch after error or success
149
queryClient.invalidateQueries({ queryKey: ['todos'] });
150
}
151
});
152
153
// Async mutation with error handling
154
const { mutateAsync: deletePost } = useMutation({
155
mutationFn: (postId) =>
156
fetch(`/api/posts/${postId}`, { method: 'DELETE' })
157
.then(res => {
158
if (!res.ok) throw new Error('Failed to delete');
159
return res.json();
160
})
161
});
162
163
// Using async mutation
164
const handleDelete = async (postId) => {
165
try {
166
await deletePost(postId);
167
router.push('/posts');
168
} catch (error) {
169
alert('Failed to delete post');
170
}
171
};
172
173
// Mutation with retry logic
174
const { mutate: uploadFile } = useMutation({
175
mutationFn: (file) => {
176
const formData = new FormData();
177
formData.append('file', file);
178
return fetch('/api/upload', {
179
method: 'POST',
180
body: formData
181
}).then(res => res.json());
182
},
183
retry: 3,
184
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
185
onError: (error, variables, context) => {
186
console.error(`Upload failed after retries:`, error);
187
}
188
});
189
190
// Multiple mutations with shared state
191
const { mutate: saveDraft, isPending: isSaving } = useMutation({
192
mutationFn: (draft) => fetch('/api/drafts', {
193
method: 'POST',
194
headers: { 'Content-Type': 'application/json' },
195
body: JSON.stringify(draft)
196
})
197
});
198
199
const { mutate: publishPost, isPending: isPublishing } = useMutation({
200
mutationFn: (post) => fetch('/api/posts', {
201
method: 'POST',
202
headers: { 'Content-Type': 'application/json' },
203
body: JSON.stringify(post)
204
})
205
});
206
207
const isBusy = computed(() => isSaving.value || isPublishing.value);
208
```
209
210
## Types
211
212
```typescript { .api }
213
// Mutation key type
214
type MutationKey = ReadonlyArray<unknown>;
215
216
// Mutation status type
217
type MutationStatus = 'idle' | 'pending' | 'success' | 'error';
218
219
// Mutation function context
220
interface MutationObserverBaseResult<TData, TError, TVariables, TContext> {
221
context: TContext | undefined;
222
data: TData | undefined;
223
error: TError | null;
224
failureCount: number;
225
failureReason: TError | null;
226
isPaused: boolean;
227
status: MutationStatus;
228
submittedAt: number;
229
variables: TVariables | undefined;
230
}
231
232
// Mutation metadata
233
interface MutationMeta extends Record<string, unknown> {
234
[key: string]: unknown;
235
}
236
237
// Base mutation options
238
interface MutationObserverOptions<TData = unknown, TError = DefaultError, TVariables = void, TContext = unknown> {
239
mutationFn?: (variables: TVariables) => Promise<TData> | TData;
240
mutationKey?: MutationKey;
241
onMutate?: (variables: TVariables) => Promise<TContext | void> | TContext | void;
242
onSuccess?: (data: TData, variables: TVariables, context: TContext | undefined) => Promise<unknown> | unknown;
243
onError?: (error: TError, variables: TVariables, context: TContext | undefined) => Promise<unknown> | unknown;
244
onSettled?: (data: TData | undefined, error: TError | null, variables: TVariables, context: TContext | undefined) => Promise<unknown> | unknown;
245
retry?: boolean | number | ((failureCount: number, error: TError) => boolean);
246
retryDelay?: number | ((retryAttempt: number, error: TError) => number);
247
throwOnError?: boolean | ((error: TError) => boolean);
248
meta?: MutationMeta;
249
}
250
251
// Vue-specific mutation options type
252
type UseMutationOptionsBase<TData, TError, TVariables, TContext> =
253
MutationObserverOptions<TData, TError, TVariables, TContext> & ShallowOption;
254
255
// Default error type
256
type DefaultError = Error;
257
```