@apollo/client 4.2.0-alpha.2 → 4.2.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +61 -0
- package/__cjs/core/ApolloClient.cjs +9 -9
- package/__cjs/core/ApolloClient.cjs.map +1 -1
- package/__cjs/core/ApolloClient.d.cts +131 -21
- package/__cjs/link/ws/index.cjs +9 -1
- package/__cjs/link/ws/index.cjs.map +1 -1
- package/__cjs/link/ws/index.d.cts +1 -1
- package/__cjs/react/hooks/useBackgroundQuery.cjs.map +1 -1
- package/__cjs/react/hooks/useBackgroundQuery.d.cts +1466 -65
- package/__cjs/react/hooks/useLazyQuery.cjs.map +1 -1
- package/__cjs/react/hooks/useLazyQuery.d.cts +346 -39
- package/__cjs/react/hooks/useLoadableQuery.cjs.map +1 -1
- package/__cjs/react/hooks/useLoadableQuery.d.cts +492 -49
- package/__cjs/react/hooks/useMutation.cjs +5 -48
- package/__cjs/react/hooks/useMutation.cjs.map +1 -1
- package/__cjs/react/hooks/useMutation.d.cts +239 -130
- package/__cjs/react/hooks/useQuery.cjs.map +1 -1
- package/__cjs/react/hooks/useQuery.d.cts +570 -40
- package/__cjs/react/hooks/useSubscription.cjs +1 -1
- package/__cjs/react/hooks/useSubscription.cjs.map +1 -1
- package/__cjs/react/hooks/useSubscription.d.cts +2 -2
- package/__cjs/react/hooks/useSuspenseQuery.cjs.map +1 -1
- package/__cjs/react/hooks/useSuspenseQuery.d.cts +734 -45
- package/__cjs/version.cjs +1 -1
- package/core/ApolloClient.d.ts +131 -21
- package/core/ApolloClient.js +9 -9
- package/core/ApolloClient.js.map +1 -1
- package/link/ws/index.d.ts +1 -1
- package/link/ws/index.js +9 -1
- package/link/ws/index.js.map +1 -1
- package/package.json +3 -7
- package/react/hooks/useBackgroundQuery.d.ts +1466 -65
- package/react/hooks/useBackgroundQuery.js.map +1 -1
- package/react/hooks/useLazyQuery.d.ts +346 -39
- package/react/hooks/useLazyQuery.js.map +1 -1
- package/react/hooks/useLoadableQuery.d.ts +492 -49
- package/react/hooks/useLoadableQuery.js.map +1 -1
- package/react/hooks/useMutation.d.ts +239 -130
- package/react/hooks/useMutation.js +5 -48
- package/react/hooks/useMutation.js.map +1 -1
- package/react/hooks/useQuery.d.ts +570 -40
- package/react/hooks/useQuery.js.map +1 -1
- package/react/hooks/useSubscription.d.ts +2 -2
- package/react/hooks/useSubscription.js +1 -1
- package/react/hooks/useSubscription.js.map +1 -1
- package/react/hooks/useSuspenseQuery.d.ts +734 -45
- package/react/hooks/useSuspenseQuery.js.map +1 -1
- package/react/hooks-compiled/useBackgroundQuery.d.ts +1466 -65
- package/react/hooks-compiled/useBackgroundQuery.js.map +1 -1
- package/react/hooks-compiled/useLazyQuery.d.ts +346 -39
- package/react/hooks-compiled/useLazyQuery.js.map +1 -1
- package/react/hooks-compiled/useLoadableQuery.d.ts +492 -49
- package/react/hooks-compiled/useLoadableQuery.js.map +1 -1
- package/react/hooks-compiled/useMutation.d.ts +239 -130
- package/react/hooks-compiled/useMutation.js +4 -47
- package/react/hooks-compiled/useMutation.js.map +1 -1
- package/react/hooks-compiled/useQuery.d.ts +570 -40
- package/react/hooks-compiled/useQuery.js.map +1 -1
- package/react/hooks-compiled/useSubscription.d.ts +2 -2
- package/react/hooks-compiled/useSubscription.js +1 -1
- package/react/hooks-compiled/useSubscription.js.map +1 -1
- package/react/hooks-compiled/useSuspenseQuery.d.ts +734 -45
- package/react/hooks-compiled/useSuspenseQuery.js.map +1 -1
- package/skills/apollo-client/SKILL.md +168 -0
- package/skills/apollo-client/references/caching.md +560 -0
- package/skills/apollo-client/references/error-handling.md +350 -0
- package/skills/apollo-client/references/fragments.md +804 -0
- package/skills/apollo-client/references/integration-client.md +336 -0
- package/skills/apollo-client/references/integration-nextjs.md +325 -0
- package/skills/apollo-client/references/integration-react-router.md +256 -0
- package/skills/apollo-client/references/integration-tanstack-start.md +378 -0
- package/skills/apollo-client/references/mutations.md +549 -0
- package/skills/apollo-client/references/queries.md +416 -0
- package/skills/apollo-client/references/state-management.md +428 -0
- package/skills/apollo-client/references/suspense-hooks.md +773 -0
- package/skills/apollo-client/references/troubleshooting.md +487 -0
- package/skills/apollo-client/references/typescript-codegen.md +133 -0
- package/version.js +1 -1
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
# Mutations Reference
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
- [useMutation Hook](#usemutation-hook)
|
|
6
|
+
- [Mutation Variables](#mutation-variables)
|
|
7
|
+
- [Loading and Error States](#loading-and-error-states)
|
|
8
|
+
- [Optimistic UI](#optimistic-ui)
|
|
9
|
+
- [Cache Updates](#cache-updates)
|
|
10
|
+
- [Refetch Queries](#refetch-queries)
|
|
11
|
+
- [Error Handling](#error-handling)
|
|
12
|
+
|
|
13
|
+
## useMutation Hook
|
|
14
|
+
|
|
15
|
+
The `useMutation` hook is used to execute GraphQL mutations.
|
|
16
|
+
|
|
17
|
+
### Basic Usage
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import { gql } from "@apollo/client";
|
|
21
|
+
import { useMutation } from "@apollo/client/react";
|
|
22
|
+
|
|
23
|
+
const ADD_TODO = gql`
|
|
24
|
+
mutation AddTodo($text: String!) {
|
|
25
|
+
addTodo(text: $text) {
|
|
26
|
+
id
|
|
27
|
+
text
|
|
28
|
+
completed
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
function AddTodo() {
|
|
34
|
+
const [addTodo, { data, loading, error }] = useMutation(ADD_TODO);
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<form
|
|
38
|
+
onSubmit={(e) => {
|
|
39
|
+
e.preventDefault();
|
|
40
|
+
const form = e.currentTarget;
|
|
41
|
+
const text = new FormData(form).get("text") as string;
|
|
42
|
+
addTodo({ variables: { text } });
|
|
43
|
+
form.reset();
|
|
44
|
+
}}
|
|
45
|
+
>
|
|
46
|
+
<input name="text" placeholder="Add todo" />
|
|
47
|
+
<button type="submit" disabled={loading}>
|
|
48
|
+
Add
|
|
49
|
+
</button>
|
|
50
|
+
{error && <p>Error: {error.message}</p>}
|
|
51
|
+
</form>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Return Tuple
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
const [
|
|
60
|
+
mutateFunction, // Function to call to execute mutation
|
|
61
|
+
{
|
|
62
|
+
data, // Mutation result data
|
|
63
|
+
loading, // True while mutation is in flight
|
|
64
|
+
error, // ApolloError if mutation failed
|
|
65
|
+
called, // True if mutation has been called
|
|
66
|
+
reset, // Reset mutation state
|
|
67
|
+
client, // Apollo Client instance
|
|
68
|
+
},
|
|
69
|
+
] = useMutation(MUTATION);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Mutation Variables
|
|
73
|
+
|
|
74
|
+
### Variables in Options
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
const [createUser] = useMutation(CREATE_USER, {
|
|
78
|
+
variables: {
|
|
79
|
+
input: {
|
|
80
|
+
name: "Default User",
|
|
81
|
+
email: "default@example.com",
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Call with default variables
|
|
87
|
+
await createUser();
|
|
88
|
+
|
|
89
|
+
// Override variables
|
|
90
|
+
await createUser({
|
|
91
|
+
variables: {
|
|
92
|
+
input: {
|
|
93
|
+
name: "Custom User",
|
|
94
|
+
email: "custom@example.com",
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### TypeScript Types
|
|
101
|
+
|
|
102
|
+
Use `TypedDocumentNode` instead of generic type parameters:
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
import { gql, TypedDocumentNode } from "@apollo/client";
|
|
106
|
+
|
|
107
|
+
interface CreateUserData {
|
|
108
|
+
createUser: {
|
|
109
|
+
id: string;
|
|
110
|
+
name: string;
|
|
111
|
+
email: string;
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
interface CreateUserVariables {
|
|
116
|
+
input: {
|
|
117
|
+
name: string;
|
|
118
|
+
email: string;
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const CREATE_USER: TypedDocumentNode<CreateUserData, CreateUserVariables> = gql`
|
|
123
|
+
mutation CreateUser($input: CreateUserInput!) {
|
|
124
|
+
createUser(input: $input) {
|
|
125
|
+
id
|
|
126
|
+
name
|
|
127
|
+
email
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
`;
|
|
131
|
+
|
|
132
|
+
const [createUser, { data, loading }] = useMutation(CREATE_USER);
|
|
133
|
+
|
|
134
|
+
const { data } = await createUser({
|
|
135
|
+
variables: {
|
|
136
|
+
input: { name: "John", email: "john@example.com" },
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// data.createUser is fully typed
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Loading and Error States
|
|
144
|
+
|
|
145
|
+
### Handling in UI
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
function CreatePost() {
|
|
149
|
+
const [createPost, { loading, error, data, reset }] =
|
|
150
|
+
useMutation(CREATE_POST);
|
|
151
|
+
|
|
152
|
+
if (data) {
|
|
153
|
+
return (
|
|
154
|
+
<div>
|
|
155
|
+
<p>Post created: {data.createPost.title}</p>
|
|
156
|
+
<button onClick={reset}>Create another</button>
|
|
157
|
+
</div>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return (
|
|
162
|
+
<form onSubmit={handleSubmit}>
|
|
163
|
+
<input name="title" disabled={loading} />
|
|
164
|
+
<textarea name="content" disabled={loading} />
|
|
165
|
+
<button type="submit" disabled={loading}>
|
|
166
|
+
{loading ? "Creating..." : "Create Post"}
|
|
167
|
+
</button>
|
|
168
|
+
{error && (
|
|
169
|
+
<div className="error">
|
|
170
|
+
<p>Failed to create post: {error.message}</p>
|
|
171
|
+
<button onClick={reset}>Try again</button>
|
|
172
|
+
</div>
|
|
173
|
+
)}
|
|
174
|
+
</form>
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Async/Await Pattern
|
|
180
|
+
|
|
181
|
+
If you only need the promise without using the hook's loading/data state, use `client.mutate` instead:
|
|
182
|
+
|
|
183
|
+
```tsx
|
|
184
|
+
import { useApolloClient } from "@apollo/client/react";
|
|
185
|
+
|
|
186
|
+
function CreatePost() {
|
|
187
|
+
const client = useApolloClient();
|
|
188
|
+
|
|
189
|
+
async function handleSubmit(formData: FormData) {
|
|
190
|
+
try {
|
|
191
|
+
const { data } = await client.mutate({
|
|
192
|
+
mutation: CREATE_POST,
|
|
193
|
+
variables: {
|
|
194
|
+
input: {
|
|
195
|
+
title: formData.get("title"),
|
|
196
|
+
content: formData.get("content"),
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
console.log("Created:", data.createPost);
|
|
201
|
+
router.push(`/posts/${data.createPost.id}`);
|
|
202
|
+
} catch (error) {
|
|
203
|
+
console.error("Failed to create post:", error);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return (
|
|
208
|
+
<form
|
|
209
|
+
onSubmit={(e) => {
|
|
210
|
+
e.preventDefault();
|
|
211
|
+
handleSubmit(new FormData(e.currentTarget));
|
|
212
|
+
}}
|
|
213
|
+
>
|
|
214
|
+
...
|
|
215
|
+
</form>
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
If you do use the hook's state, e.g. because you want to render the `loading` state, errors or returned `data`, you can also use the `useMutation` hook with `async..await` in your handler:
|
|
221
|
+
|
|
222
|
+
```tsx
|
|
223
|
+
function CreatePost() {
|
|
224
|
+
const [createPost, { loading }] = useMutation(CREATE_POST);
|
|
225
|
+
|
|
226
|
+
async function handleSubmit(formData: FormData) {
|
|
227
|
+
try {
|
|
228
|
+
const { data } = await createPost({
|
|
229
|
+
variables: {
|
|
230
|
+
input: {
|
|
231
|
+
title: formData.get("title"),
|
|
232
|
+
content: formData.get("content"),
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
console.log("Created:", data.createPost);
|
|
237
|
+
router.push(`/posts/${data.createPost.id}`);
|
|
238
|
+
} catch (error) {
|
|
239
|
+
console.error("Failed to create post:", error);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return (
|
|
244
|
+
<form
|
|
245
|
+
onSubmit={(e) => {
|
|
246
|
+
e.preventDefault();
|
|
247
|
+
handleSubmit(new FormData(e.currentTarget));
|
|
248
|
+
}}
|
|
249
|
+
>
|
|
250
|
+
<button type="submit" disabled={loading}>
|
|
251
|
+
{loading ? "Creating..." : "Create Post"}
|
|
252
|
+
</button>
|
|
253
|
+
</form>
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Optimistic UI
|
|
259
|
+
|
|
260
|
+
Optimistic UI immediately reflects the expected result of a mutation before the server responds.
|
|
261
|
+
|
|
262
|
+
### Basic Optimistic Response
|
|
263
|
+
|
|
264
|
+
**Important**: `optimisticResponse` needs to be a full valid response for the mutation. A partial result might result in subtle errors.
|
|
265
|
+
|
|
266
|
+
```tsx
|
|
267
|
+
const [addTodo] = useMutation(ADD_TODO, {
|
|
268
|
+
optimisticResponse: {
|
|
269
|
+
addTodo: {
|
|
270
|
+
__typename: "Todo",
|
|
271
|
+
id: "temp-id",
|
|
272
|
+
text: "New todo",
|
|
273
|
+
completed: false,
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Dynamic Optimistic Response
|
|
280
|
+
|
|
281
|
+
```tsx
|
|
282
|
+
function TodoList() {
|
|
283
|
+
const [addTodo] = useMutation(ADD_TODO);
|
|
284
|
+
|
|
285
|
+
const handleAdd = (text: string) => {
|
|
286
|
+
addTodo({
|
|
287
|
+
variables: { text },
|
|
288
|
+
optimisticResponse: {
|
|
289
|
+
addTodo: {
|
|
290
|
+
__typename: "Todo",
|
|
291
|
+
id: `temp-${Date.now()}`,
|
|
292
|
+
text,
|
|
293
|
+
completed: false,
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
});
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
return <AddTodoForm onAdd={handleAdd} />;
|
|
300
|
+
}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Optimistic Response with Cache Update
|
|
304
|
+
|
|
305
|
+
```tsx
|
|
306
|
+
const [toggleTodo] = useMutation(TOGGLE_TODO, {
|
|
307
|
+
optimisticResponse: ({ id }) => ({
|
|
308
|
+
toggleTodo: {
|
|
309
|
+
__typename: "Todo",
|
|
310
|
+
id,
|
|
311
|
+
completed: true, // Assume success
|
|
312
|
+
},
|
|
313
|
+
}),
|
|
314
|
+
update: (cache, { data }) => {
|
|
315
|
+
// This runs twice: once with optimistic data, once with server data
|
|
316
|
+
cache.modify({
|
|
317
|
+
id: cache.identify(data.toggleTodo),
|
|
318
|
+
fields: {
|
|
319
|
+
completed: () => data.toggleTodo.completed,
|
|
320
|
+
},
|
|
321
|
+
});
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
## Cache Updates
|
|
327
|
+
|
|
328
|
+
### Using update Function
|
|
329
|
+
|
|
330
|
+
```tsx
|
|
331
|
+
const [addTodo] = useMutation(ADD_TODO, {
|
|
332
|
+
update: (cache, { data }) => {
|
|
333
|
+
// Read existing todos from cache
|
|
334
|
+
const existingTodos = cache.readQuery<{ todos: Todo[] }>({
|
|
335
|
+
query: GET_TODOS,
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
// Write updated list back to cache
|
|
339
|
+
cache.writeQuery({
|
|
340
|
+
query: GET_TODOS,
|
|
341
|
+
data: {
|
|
342
|
+
todos: [...(existingTodos?.todos ?? []), data.addTodo],
|
|
343
|
+
},
|
|
344
|
+
});
|
|
345
|
+
},
|
|
346
|
+
});
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### cache.modify
|
|
350
|
+
|
|
351
|
+
```tsx
|
|
352
|
+
const [deleteTodo] = useMutation(DELETE_TODO, {
|
|
353
|
+
update: (cache, { data }) => {
|
|
354
|
+
cache.modify({
|
|
355
|
+
fields: {
|
|
356
|
+
todos: (existingTodos: Reference[], { readField }) => {
|
|
357
|
+
return existingTodos.filter(
|
|
358
|
+
(todoRef) => readField("id", todoRef) !== data.deleteTodo.id
|
|
359
|
+
);
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
});
|
|
363
|
+
},
|
|
364
|
+
});
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### cache.evict
|
|
368
|
+
|
|
369
|
+
```tsx
|
|
370
|
+
const [deleteUser] = useMutation(DELETE_USER, {
|
|
371
|
+
update: (cache, { data }) => {
|
|
372
|
+
// Remove the user object from cache entirely
|
|
373
|
+
cache.evict({ id: cache.identify(data.deleteUser) });
|
|
374
|
+
// Clean up dangling references
|
|
375
|
+
cache.gc();
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### Updating Related Queries
|
|
381
|
+
|
|
382
|
+
```tsx
|
|
383
|
+
const [createPost] = useMutation(CREATE_POST, {
|
|
384
|
+
update: (cache, { data }) => {
|
|
385
|
+
// Update author's post count
|
|
386
|
+
cache.modify({
|
|
387
|
+
id: cache.identify({ __typename: "User", id: data.createPost.authorId }),
|
|
388
|
+
fields: {
|
|
389
|
+
postCount: (existing) => existing + 1,
|
|
390
|
+
posts: (existing, { toReference }) => [
|
|
391
|
+
...existing,
|
|
392
|
+
toReference(data.createPost),
|
|
393
|
+
],
|
|
394
|
+
},
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
// Add to feed
|
|
398
|
+
cache.modify({
|
|
399
|
+
fields: {
|
|
400
|
+
feed: (existing, { toReference }) => [
|
|
401
|
+
toReference(data.createPost),
|
|
402
|
+
...existing,
|
|
403
|
+
],
|
|
404
|
+
},
|
|
405
|
+
});
|
|
406
|
+
},
|
|
407
|
+
});
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
## Refetch Queries
|
|
411
|
+
|
|
412
|
+
### Basic Refetch
|
|
413
|
+
|
|
414
|
+
There are three refetch notations:
|
|
415
|
+
|
|
416
|
+
- **String**: `refetchQueries: ['getTodos']` - refetches all active `getTodos` queries
|
|
417
|
+
- **Query document**: `refetchQueries: [GET_TODOS]` - refetches all active queries using this document
|
|
418
|
+
- **Object**: `refetchQueries: [{ query: GET_TODOS }, { query: GET_TODOS, variables: { page: 25 } }]` - **fetches** the query, regardless if it's actively used in the UI
|
|
419
|
+
|
|
420
|
+
```tsx
|
|
421
|
+
const [addTodo] = useMutation(ADD_TODO, {
|
|
422
|
+
// Refetch all active GET_TODOS queries
|
|
423
|
+
refetchQueries: ["getTodos"],
|
|
424
|
+
// Or: refetchQueries: [GET_TODOS],
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
// Fetch specific query with variables (even if not active)
|
|
428
|
+
const [addTodo] = useMutation(ADD_TODO, {
|
|
429
|
+
refetchQueries: [{ query: GET_TODOS }, { query: GET_TODO_COUNT }],
|
|
430
|
+
});
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### Conditional Refetch
|
|
434
|
+
|
|
435
|
+
```tsx
|
|
436
|
+
const [addTodo] = useMutation(ADD_TODO, {
|
|
437
|
+
refetchQueries: (result) => {
|
|
438
|
+
if (result.data?.addTodo.priority === "HIGH") {
|
|
439
|
+
return [{ query: GET_HIGH_PRIORITY_TODOS }];
|
|
440
|
+
}
|
|
441
|
+
return [{ query: GET_TODOS }];
|
|
442
|
+
},
|
|
443
|
+
});
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
### Refetch Active Queries
|
|
447
|
+
|
|
448
|
+
```tsx
|
|
449
|
+
const [addTodo] = useMutation(ADD_TODO, {
|
|
450
|
+
refetchQueries: "active", // Refetch all active queries
|
|
451
|
+
// Or: 'all' to refetch all queries (including inactive)
|
|
452
|
+
});
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
### awaitRefetchQueries
|
|
456
|
+
|
|
457
|
+
```tsx
|
|
458
|
+
const [addTodo] = useMutation(ADD_TODO, {
|
|
459
|
+
refetchQueries: [{ query: GET_TODOS }],
|
|
460
|
+
awaitRefetchQueries: true, // Wait for refetch before resolving mutation
|
|
461
|
+
});
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
### onQueryUpdated
|
|
465
|
+
|
|
466
|
+
Returning `true` from `onQueryUpdated` causes a refetch. Don't call `refetch()` manually inside `onQueryUpdated`, as it won't retain the query and might cancel it early.
|
|
467
|
+
|
|
468
|
+
```tsx
|
|
469
|
+
const [addTodo] = useMutation(ADD_TODO, {
|
|
470
|
+
update: (cache, { data }) => {
|
|
471
|
+
// Update cache...
|
|
472
|
+
},
|
|
473
|
+
onQueryUpdated: (observableQuery) => {
|
|
474
|
+
// Called for each query affected by cache update
|
|
475
|
+
console.log(`Query ${observableQuery.queryName} was updated`);
|
|
476
|
+
// Return true to refetch
|
|
477
|
+
return true;
|
|
478
|
+
},
|
|
479
|
+
});
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
## Error Handling
|
|
483
|
+
|
|
484
|
+
### Error Policy
|
|
485
|
+
|
|
486
|
+
```tsx
|
|
487
|
+
const [createUser, { loading }] = useMutation(CREATE_USER, {
|
|
488
|
+
errorPolicy: "all", // Return both data and errors
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
const { data, errors } = await createUser({
|
|
492
|
+
variables: { input },
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
// Handle partial success
|
|
496
|
+
if (data?.createUser) {
|
|
497
|
+
console.log("User created:", data.createUser);
|
|
498
|
+
}
|
|
499
|
+
if (errors) {
|
|
500
|
+
console.warn("Some errors occurred:", errors);
|
|
501
|
+
}
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
### onError Callback
|
|
505
|
+
|
|
506
|
+
```tsx
|
|
507
|
+
const [createUser] = useMutation(CREATE_USER, {
|
|
508
|
+
onError: (error) => {
|
|
509
|
+
// Handle error globally
|
|
510
|
+
toast.error(`Failed to create user: ${error.message}`);
|
|
511
|
+
|
|
512
|
+
// Log to error tracking service
|
|
513
|
+
Sentry.captureException(error);
|
|
514
|
+
},
|
|
515
|
+
onCompleted: (data) => {
|
|
516
|
+
toast.success(`User ${data.createUser.name} created!`);
|
|
517
|
+
},
|
|
518
|
+
});
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
### Field-Level Errors
|
|
522
|
+
|
|
523
|
+
```tsx
|
|
524
|
+
const [createUser] = useMutation(CREATE_USER, {
|
|
525
|
+
errorPolicy: "all",
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
const handleSubmit = async (input: CreateUserInput) => {
|
|
529
|
+
const { data, errors } = await createUser({
|
|
530
|
+
variables: { input },
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
// Handle GraphQL validation errors
|
|
534
|
+
const fieldErrors = errors?.reduce(
|
|
535
|
+
(acc, error) => {
|
|
536
|
+
const field = error.extensions?.field as string;
|
|
537
|
+
if (field) {
|
|
538
|
+
acc[field] = error.message;
|
|
539
|
+
}
|
|
540
|
+
return acc;
|
|
541
|
+
},
|
|
542
|
+
{} as Record<string, string>
|
|
543
|
+
);
|
|
544
|
+
|
|
545
|
+
if (fieldErrors?.email) {
|
|
546
|
+
setEmailError(fieldErrors.email);
|
|
547
|
+
}
|
|
548
|
+
};
|
|
549
|
+
```
|