@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.
Files changed (78) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/__cjs/core/ApolloClient.cjs +9 -9
  3. package/__cjs/core/ApolloClient.cjs.map +1 -1
  4. package/__cjs/core/ApolloClient.d.cts +131 -21
  5. package/__cjs/link/ws/index.cjs +9 -1
  6. package/__cjs/link/ws/index.cjs.map +1 -1
  7. package/__cjs/link/ws/index.d.cts +1 -1
  8. package/__cjs/react/hooks/useBackgroundQuery.cjs.map +1 -1
  9. package/__cjs/react/hooks/useBackgroundQuery.d.cts +1466 -65
  10. package/__cjs/react/hooks/useLazyQuery.cjs.map +1 -1
  11. package/__cjs/react/hooks/useLazyQuery.d.cts +346 -39
  12. package/__cjs/react/hooks/useLoadableQuery.cjs.map +1 -1
  13. package/__cjs/react/hooks/useLoadableQuery.d.cts +492 -49
  14. package/__cjs/react/hooks/useMutation.cjs +5 -48
  15. package/__cjs/react/hooks/useMutation.cjs.map +1 -1
  16. package/__cjs/react/hooks/useMutation.d.cts +239 -130
  17. package/__cjs/react/hooks/useQuery.cjs.map +1 -1
  18. package/__cjs/react/hooks/useQuery.d.cts +570 -40
  19. package/__cjs/react/hooks/useSubscription.cjs +1 -1
  20. package/__cjs/react/hooks/useSubscription.cjs.map +1 -1
  21. package/__cjs/react/hooks/useSubscription.d.cts +2 -2
  22. package/__cjs/react/hooks/useSuspenseQuery.cjs.map +1 -1
  23. package/__cjs/react/hooks/useSuspenseQuery.d.cts +734 -45
  24. package/__cjs/version.cjs +1 -1
  25. package/core/ApolloClient.d.ts +131 -21
  26. package/core/ApolloClient.js +9 -9
  27. package/core/ApolloClient.js.map +1 -1
  28. package/link/ws/index.d.ts +1 -1
  29. package/link/ws/index.js +9 -1
  30. package/link/ws/index.js.map +1 -1
  31. package/package.json +3 -7
  32. package/react/hooks/useBackgroundQuery.d.ts +1466 -65
  33. package/react/hooks/useBackgroundQuery.js.map +1 -1
  34. package/react/hooks/useLazyQuery.d.ts +346 -39
  35. package/react/hooks/useLazyQuery.js.map +1 -1
  36. package/react/hooks/useLoadableQuery.d.ts +492 -49
  37. package/react/hooks/useLoadableQuery.js.map +1 -1
  38. package/react/hooks/useMutation.d.ts +239 -130
  39. package/react/hooks/useMutation.js +5 -48
  40. package/react/hooks/useMutation.js.map +1 -1
  41. package/react/hooks/useQuery.d.ts +570 -40
  42. package/react/hooks/useQuery.js.map +1 -1
  43. package/react/hooks/useSubscription.d.ts +2 -2
  44. package/react/hooks/useSubscription.js +1 -1
  45. package/react/hooks/useSubscription.js.map +1 -1
  46. package/react/hooks/useSuspenseQuery.d.ts +734 -45
  47. package/react/hooks/useSuspenseQuery.js.map +1 -1
  48. package/react/hooks-compiled/useBackgroundQuery.d.ts +1466 -65
  49. package/react/hooks-compiled/useBackgroundQuery.js.map +1 -1
  50. package/react/hooks-compiled/useLazyQuery.d.ts +346 -39
  51. package/react/hooks-compiled/useLazyQuery.js.map +1 -1
  52. package/react/hooks-compiled/useLoadableQuery.d.ts +492 -49
  53. package/react/hooks-compiled/useLoadableQuery.js.map +1 -1
  54. package/react/hooks-compiled/useMutation.d.ts +239 -130
  55. package/react/hooks-compiled/useMutation.js +4 -47
  56. package/react/hooks-compiled/useMutation.js.map +1 -1
  57. package/react/hooks-compiled/useQuery.d.ts +570 -40
  58. package/react/hooks-compiled/useQuery.js.map +1 -1
  59. package/react/hooks-compiled/useSubscription.d.ts +2 -2
  60. package/react/hooks-compiled/useSubscription.js +1 -1
  61. package/react/hooks-compiled/useSubscription.js.map +1 -1
  62. package/react/hooks-compiled/useSuspenseQuery.d.ts +734 -45
  63. package/react/hooks-compiled/useSuspenseQuery.js.map +1 -1
  64. package/skills/apollo-client/SKILL.md +168 -0
  65. package/skills/apollo-client/references/caching.md +560 -0
  66. package/skills/apollo-client/references/error-handling.md +350 -0
  67. package/skills/apollo-client/references/fragments.md +804 -0
  68. package/skills/apollo-client/references/integration-client.md +336 -0
  69. package/skills/apollo-client/references/integration-nextjs.md +325 -0
  70. package/skills/apollo-client/references/integration-react-router.md +256 -0
  71. package/skills/apollo-client/references/integration-tanstack-start.md +378 -0
  72. package/skills/apollo-client/references/mutations.md +549 -0
  73. package/skills/apollo-client/references/queries.md +416 -0
  74. package/skills/apollo-client/references/state-management.md +428 -0
  75. package/skills/apollo-client/references/suspense-hooks.md +773 -0
  76. package/skills/apollo-client/references/troubleshooting.md +487 -0
  77. package/skills/apollo-client/references/typescript-codegen.md +133 -0
  78. package/version.js +1 -1
@@ -0,0 +1,773 @@
1
+ # Suspense Hooks Reference
2
+
3
+ > **Note**: Suspense hooks are the recommended approach for data fetching in modern React applications (React 18+). They provide cleaner code, better loading state handling, and enable streaming SSR.
4
+
5
+ ## Table of Contents
6
+
7
+ - [useSuspenseQuery Hook](#usesuspensequery-hook)
8
+ - [useBackgroundQuery and useReadQuery](#usebackgroundquery-and-usereadquery)
9
+ - [useLoadableQuery](#useloadablequery)
10
+ - [createQueryPreloader](#createquerypreloader)
11
+ - [useQueryRefHandlers](#usequeryrefhandlers)
12
+ - [Distinguishing Queries with queryKey](#distinguishing-queries-with-querykey)
13
+ - [Suspense Boundaries and Error Handling](#suspense-boundaries-and-error-handling)
14
+ - [Transitions](#transitions)
15
+ - [Avoiding Request Waterfalls](#avoiding-request-waterfalls)
16
+ - [Fetch Policies](#fetch-policies)
17
+ - [Streaming SSR or React Server Components](#streaming-ssr-or-react-server-components)
18
+ - [Conditional Queries](#conditional-queries)
19
+
20
+ ## useSuspenseQuery Hook
21
+
22
+ The `useSuspenseQuery` hook is the Suspense-ready replacement for `useQuery`. It initiates a network request and causes the component calling it to suspend while the request is made. Unlike `useQuery`, it does not return `loading` states—these are handled by React's Suspense boundaries and error boundaries.
23
+
24
+ ### Basic Usage
25
+
26
+ ```tsx
27
+ import { Suspense } from "react";
28
+ import { useSuspenseQuery } from "@apollo/client/react";
29
+ import { GET_DOG } from "./queries.generated";
30
+
31
+ function App() {
32
+ return (
33
+ <Suspense fallback={<div>Loading...</div>}>
34
+ <Dog id="3" />
35
+ </Suspense>
36
+ );
37
+ }
38
+
39
+ function Dog({ id }: { id: string }) {
40
+ const { data } = useSuspenseQuery(GET_DOG, {
41
+ variables: { id },
42
+ });
43
+
44
+ // data is always defined when this component renders
45
+ return <div>Name: {data.dog.name}</div>;
46
+ }
47
+ ```
48
+
49
+ ### Return Object
50
+
51
+ ```typescript
52
+ const {
53
+ data, // Query result data
54
+ dataState, // With default options: "complete" | "streaming"
55
+ // With returnPartialData: also "partial"
56
+ // With errorPolicy "all" or "ignore": also "empty"
57
+ error, // ApolloError (only when errorPolicy is "all" or "ignore")
58
+ networkStatus, // NetworkStatus.ready, NetworkStatus.loading, etc.
59
+ client, // Apollo Client instance
60
+ refetch, // Function to re-execute query
61
+ fetchMore, // Function for pagination
62
+ } = useSuspenseQuery(QUERY, options);
63
+ ```
64
+
65
+ ### Key Differences from useQuery
66
+
67
+ - **No `loading` boolean**: Component suspends instead of returning `loading: true`
68
+ - **Error handling**: With default `errorPolicy` (`none`), errors are thrown and caught by error boundaries. With `errorPolicy: "all"` or `"ignore"`, the `error` property is returned and `data` may be `undefined`.
69
+ - **`data` availability**: With default `errorPolicy` (`none`), `data` is guaranteed to be present when the component renders. With `errorPolicy: "all"` or `"ignore"`, when `dataState` is `empty`, `data` may be `undefined`.
70
+ - **Suspense boundaries**: Must wrap component with `<Suspense>` to handle loading state
71
+
72
+ ### Changing Variables
73
+
74
+ When variables change, `useSuspenseQuery` automatically re-runs the query. If the data is not in the cache, the component suspends again.
75
+
76
+ ```tsx
77
+ import { useState } from "react";
78
+ import { GET_DOGS } from "./queries.generated";
79
+
80
+ function DogSelector() {
81
+ const { data } = useSuspenseQuery(GET_DOGS);
82
+ const [selectedDog, setSelectedDog] = useState(data.dogs[0].id);
83
+
84
+ return (
85
+ <>
86
+ <select
87
+ value={selectedDog}
88
+ onChange={(e) => setSelectedDog(e.target.value)}
89
+ >
90
+ {data.dogs.map((dog) => (
91
+ <option key={dog.id} value={dog.id}>
92
+ {dog.name}
93
+ </option>
94
+ ))}
95
+ </select>
96
+ <Suspense fallback={<div>Loading...</div>}>
97
+ <Dog id={selectedDog} />
98
+ </Suspense>
99
+ </>
100
+ );
101
+ }
102
+
103
+ function Dog({ id }: { id: string }) {
104
+ const { data } = useSuspenseQuery(GET_DOG, {
105
+ variables: { id },
106
+ });
107
+
108
+ return (
109
+ <>
110
+ <div>Name: {data.dog.name}</div>
111
+ <div>Breed: {data.dog.breed}</div>
112
+ </>
113
+ );
114
+ }
115
+ ```
116
+
117
+ ### Rendering Partial Data
118
+
119
+ Use `returnPartialData` to render immediately with partial cache data instead of suspending. The component will still suspend if there is no data in the cache.
120
+
121
+ ```tsx
122
+ function Dog({ id }: { id: string }) {
123
+ const { data } = useSuspenseQuery(GET_DOG, {
124
+ variables: { id },
125
+ returnPartialData: true,
126
+ });
127
+
128
+ return (
129
+ <>
130
+ <div>Name: {data.dog?.name ?? "Unknown"}</div>
131
+ {data.dog?.breed && <div>Breed: {data.dog.breed}</div>}
132
+ </>
133
+ );
134
+ }
135
+ ```
136
+
137
+ ## useBackgroundQuery and useReadQuery
138
+
139
+ Use `useBackgroundQuery` with `useReadQuery` to avoid request waterfalls by starting a query in a parent component and reading the result in a child component. This pattern enables the parent to start fetching data before the child component renders.
140
+
141
+ ### Basic Usage
142
+
143
+ ```tsx
144
+ import { Suspense } from "react";
145
+ import { useBackgroundQuery, useReadQuery } from "@apollo/client/react";
146
+
147
+ function Parent() {
148
+ // Start fetching immediately
149
+ const [queryRef] = useBackgroundQuery(GET_DOG, {
150
+ variables: { id: "3" },
151
+ });
152
+
153
+ return (
154
+ <Suspense fallback={<div>Loading...</div>}>
155
+ <Child queryRef={queryRef} />
156
+ </Suspense>
157
+ );
158
+ }
159
+
160
+ function Child({ queryRef }: { queryRef: QueryRef<DogData> }) {
161
+ // Read the query result
162
+ const { data } = useReadQuery(queryRef);
163
+
164
+ return <div>Name: {data.dog.name}</div>;
165
+ }
166
+ ```
167
+
168
+ ### When to Use
169
+
170
+ - **Avoiding waterfalls**: Start fetching data in a parent (preferably above a suspense boundary) before child components render
171
+ - **Preloading data**: Begin fetching before the component that needs the data is ready
172
+ - **Parallel queries**: Start multiple queries at once in a parent component
173
+
174
+ ### Return Values
175
+
176
+ `useBackgroundQuery` returns a tuple:
177
+
178
+ ```typescript
179
+ const [
180
+ queryRef, // QueryRef to pass to useReadQuery
181
+ { refetch, fetchMore, subscribeToMore }, // Helper functions
182
+ ] = useBackgroundQuery(QUERY, options);
183
+ ```
184
+
185
+ `useReadQuery` returns the query result:
186
+
187
+ ```typescript
188
+ const {
189
+ data, // Query result data (always defined)
190
+ dataState, // "complete" | "streaming" | "partial" | "empty"
191
+ error, // ApolloError (if errorPolicy allows)
192
+ networkStatus, // Detailed network state (1-8)
193
+ } = useReadQuery(queryRef);
194
+ ```
195
+
196
+ ## useLoadableQuery
197
+
198
+ Use `useLoadableQuery` to imperatively load a query in response to a user interaction (like a button click) instead of on component mount.
199
+
200
+ ### Basic Usage
201
+
202
+ ```tsx
203
+ import { Suspense } from "react";
204
+ import { useLoadableQuery, useReadQuery } from "@apollo/client/react";
205
+ import { GET_GREETING } from "./queries.generated";
206
+
207
+ function App() {
208
+ const [loadGreeting, queryRef] = useLoadableQuery(GET_GREETING);
209
+
210
+ return (
211
+ <>
212
+ <button
213
+ onClick={() => loadGreeting({ variables: { language: "english" } })}
214
+ >
215
+ Load Greeting
216
+ </button>
217
+ <Suspense fallback={<div>Loading...</div>}>
218
+ {queryRef && <Greeting queryRef={queryRef} />}
219
+ </Suspense>
220
+ </>
221
+ );
222
+ }
223
+
224
+ function Greeting({ queryRef }: { queryRef: QueryRef<GreetingData> }) {
225
+ const { data } = useReadQuery(queryRef);
226
+
227
+ return <div>{data.greeting.message}</div>;
228
+ }
229
+ ```
230
+
231
+ ### Return Values
232
+
233
+ ```typescript
234
+ const [
235
+ loadQuery, // Function to load the query
236
+ queryRef, // QueryRef (null until loadQuery is called)
237
+ { refetch, fetchMore, subscribeToMore, reset }, // Helper functions
238
+ ] = useLoadableQuery(QUERY, options);
239
+ ```
240
+
241
+ ### When to Use
242
+
243
+ - **User-triggered fetching**: Load data in response to user actions
244
+ - **Lazy loading**: Defer data fetching until it's actually needed
245
+ - **Progressive disclosure**: Load data for UI elements that may not be initially visible
246
+
247
+ ## createQueryPreloader
248
+
249
+ The `createQueryPreloader` function creates a `preloadQuery` function that can be used to initiate queries outside of React components. This is useful for preloading data before a component renders, such as in route loaders or event handlers.
250
+
251
+ ### Basic Usage
252
+
253
+ ```tsx
254
+ import { ApolloClient, InMemoryCache } from "@apollo/client";
255
+ import { createQueryPreloader } from "@apollo/client/react";
256
+
257
+ const client = new ApolloClient({
258
+ uri: "https://your-graphql-endpoint.com/graphql",
259
+ cache: new InMemoryCache(),
260
+ });
261
+
262
+ // Create a preload function
263
+ export const preloadQuery = createQueryPreloader(client);
264
+ ```
265
+
266
+ ### Using preloadQuery with Route Loaders
267
+
268
+ > **Note**: This example applies to React Router in non-framework mode. For React Router framework mode, see [integration-react-router.md](./integration-react-router.md).
269
+
270
+ Use the preload function with React Router's `loader` function to begin loading data during route transitions:
271
+
272
+ ```tsx
273
+ import { preloadQuery } from "@/lib/apollo-client";
274
+ import { GET_DOG } from "./queries.generated";
275
+
276
+ // React Router loader function
277
+ export async function loader({ params }: { params: { id: string } }) {
278
+ return preloadQuery({
279
+ query: GET_DOG,
280
+ variables: { id: params.id },
281
+ });
282
+ }
283
+
284
+ // Route component
285
+ export default function DogRoute() {
286
+ const queryRef = useLoaderData();
287
+
288
+ return (
289
+ <Suspense fallback={<div>Loading...</div>}>
290
+ <DogDetails queryRef={queryRef} />
291
+ </Suspense>
292
+ );
293
+ }
294
+
295
+ function DogDetails({ queryRef }: { queryRef: QueryRef<DogData> }) {
296
+ const { data } = useReadQuery(queryRef);
297
+
298
+ return (
299
+ <div>
300
+ <h1>{data.dog.name}</h1>
301
+ <p>Breed: {data.dog.breed}</p>
302
+ </div>
303
+ );
304
+ }
305
+ ```
306
+
307
+ ### Preventing Route Transitions Until Query Loads
308
+
309
+ Use `preloadQuery.toPromise(queryRef)` to prevent route transitions until the query finishes loading:
310
+
311
+ ```tsx
312
+ export async function loader({ params }: { params: { id: string } }) {
313
+ const queryRef = preloadQuery({
314
+ query: GET_DOG,
315
+ variables: { id: params.id },
316
+ });
317
+
318
+ // Wait for the query to complete before transitioning
319
+ return preloadQuery.toPromise(queryRef);
320
+ }
321
+ ```
322
+
323
+ When `preloadQuery.toPromise()` is used, the route transition waits for the query to complete, and the data renders immediately without showing a loading fallback.
324
+
325
+ > **Note**: `preloadQuery.toPromise()` resolves with the `queryRef` itself (not the data) to encourage using `useReadQuery` for cache updates. If you need raw query data in your loader, use `client.query()` directly.
326
+
327
+ ### With Next.js Server Components
328
+
329
+ > **Note**: For Next.js App Router, use the `PreloadQuery` component from `@apollo/client-integration-nextjs` instead. See [integration-nextjs.md](./integration-nextjs.md) for details.
330
+
331
+ ## useQueryRefHandlers
332
+
333
+ The `useQueryRefHandlers` hook provides access to `refetch` and `fetchMore` functions for queries initiated with `preloadQuery`, `useBackgroundQuery`, or `useLoadableQuery`. This is useful when you need to refetch or paginate data in components where the `queryRef` is passed through.
334
+
335
+ > **Important:** Always call `useQueryRefHandlers` before `useReadQuery`. These two hooks interact with the same `queryRef`, and calling them in the wrong order could cause subtle bugs.
336
+
337
+ ### Basic Usage
338
+
339
+ ```tsx
340
+ import { useQueryRefHandlers } from "@apollo/client/react";
341
+
342
+ function Breeds({ queryRef }: { queryRef: QueryRef<BreedsData> }) {
343
+ const { refetch } = useQueryRefHandlers(queryRef);
344
+ const { data } = useReadQuery(queryRef);
345
+ const [isPending, startTransition] = useTransition();
346
+
347
+ return (
348
+ <div>
349
+ <button
350
+ disabled={isPending}
351
+ onClick={() => {
352
+ startTransition(() => {
353
+ refetch();
354
+ });
355
+ }}
356
+ >
357
+ {isPending ? "Refetching..." : "Refetch breeds"}
358
+ </button>
359
+ <ul>
360
+ {data.breeds.map((breed) => (
361
+ <li key={breed.id}>{breed.name}</li>
362
+ ))}
363
+ </ul>
364
+ </div>
365
+ );
366
+ }
367
+ ```
368
+
369
+ ### With Pagination
370
+
371
+ Use `fetchMore` to implement pagination:
372
+
373
+ ```tsx
374
+ function Posts({ queryRef }: { queryRef: QueryRef<PostsData> }) {
375
+ const { fetchMore } = useQueryRefHandlers(queryRef);
376
+ const { data } = useReadQuery(queryRef);
377
+ const [isPending, startTransition] = useTransition();
378
+
379
+ return (
380
+ <div>
381
+ <ul>
382
+ {data.posts.map((post) => (
383
+ <li key={post.id}>{post.title}</li>
384
+ ))}
385
+ </ul>
386
+ <button
387
+ disabled={isPending}
388
+ onClick={() => {
389
+ startTransition(() => {
390
+ fetchMore({
391
+ variables: {
392
+ offset: data.posts.length,
393
+ },
394
+ });
395
+ });
396
+ }}
397
+ >
398
+ {isPending ? "Loading..." : "Load more"}
399
+ </button>
400
+ </div>
401
+ );
402
+ }
403
+ ```
404
+
405
+ ### When to Use
406
+
407
+ - **Preloaded queries**: Access refetch/fetchMore for queries initiated with `preloadQuery`
408
+ - **Background queries**: Use in child components receiving `queryRef` from `useBackgroundQuery`
409
+ - **Loadable queries**: Refetch or paginate queries initiated with `useLoadableQuery`
410
+ - **React transitions**: Integrate with transitions to avoid showing loading fallbacks during refetches
411
+
412
+ ## Distinguishing Queries with queryKey
413
+
414
+ Apollo Client uses the combination of `query` and `variables` to uniquely identify each query. When multiple components use the same `query` and `variables`, they share the same identity and suspend at the same time, regardless of which component initiates the request.
415
+
416
+ Use the `queryKey` option to ensure each hook has a unique identity:
417
+
418
+ ```tsx
419
+ function UserProfile() {
420
+ // First query with unique key
421
+ const { data: userData } = useSuspenseQuery(GET_USER, {
422
+ variables: { id: "1" },
423
+ queryKey: ["user-profile"],
424
+ });
425
+
426
+ // Second query with same query and variables but different key
427
+ const { data: userPreview } = useSuspenseQuery(GET_USER, {
428
+ variables: { id: "1" },
429
+ queryKey: ["user-preview"],
430
+ });
431
+
432
+ return (
433
+ <div>
434
+ <UserCard user={userData.user} />
435
+ <UserSidebar user={userPreview.user} />
436
+ </div>
437
+ );
438
+ }
439
+ ```
440
+
441
+ ### When to Use
442
+
443
+ - **Multiple instances**: When rendering multiple components that use the same query and variables
444
+ - **Preventing shared suspension**: When you want independent control over when each query suspends
445
+ - **Separate cache entries**: When you need to maintain separate cache states for the same query
446
+
447
+ > **Note**: Each item in the `queryKey` array must be a stable identifier to prevent infinite fetches.
448
+
449
+ ## Suspense Boundaries and Error Handling
450
+
451
+ ### Suspense Boundaries
452
+
453
+ Wrap components that use Suspense hooks with `<Suspense>` boundaries to handle loading states. Place boundaries strategically to control the granularity of loading indicators.
454
+
455
+ ```tsx
456
+ function App() {
457
+ return (
458
+ <>
459
+ {/* Top-level loading for entire page */}
460
+ <Suspense fallback={<PageSpinner />}>
461
+ <Header />
462
+ <Content />
463
+ </Suspense>
464
+ </>
465
+ );
466
+ }
467
+
468
+ function Content() {
469
+ return (
470
+ <>
471
+ <MainSection />
472
+ {/* Granular loading for sidebar */}
473
+ <Suspense fallback={<SidebarSkeleton />}>
474
+ <Sidebar />
475
+ </Suspense>
476
+ </>
477
+ );
478
+ }
479
+ ```
480
+
481
+ ### Error Boundaries
482
+
483
+ Suspense hooks throw errors to React error boundaries instead of returning them. Use error boundaries to handle GraphQL errors.
484
+
485
+ ```tsx
486
+ import { ErrorBoundary } from "react-error-boundary";
487
+
488
+ function App() {
489
+ return (
490
+ <ErrorBoundary
491
+ fallback={({ error }) => (
492
+ <div>
493
+ <h2>Something went wrong</h2>
494
+ <p>{error.message}</p>
495
+ </div>
496
+ )}
497
+ >
498
+ <Suspense fallback={<div>Loading...</div>}>
499
+ <Dog id="3" />
500
+ </Suspense>
501
+ </ErrorBoundary>
502
+ );
503
+ }
504
+ ```
505
+
506
+ ### Custom Error Policies
507
+
508
+ Use `errorPolicy` to control how errors are handled:
509
+
510
+ ```tsx
511
+ function Dog({ id }: { id: string }) {
512
+ const { data, error } = useSuspenseQuery(GET_DOG, {
513
+ variables: { id },
514
+ errorPolicy: "all", // Return both data and errors
515
+ });
516
+
517
+ return (
518
+ <>
519
+ <div>Name: {data?.dog?.name ?? "Unknown"}</div>
520
+ {error && <div>Warning: {error.message}</div>}
521
+ </>
522
+ );
523
+ }
524
+ ```
525
+
526
+ ## Transitions
527
+
528
+ Use React transitions to avoid showing loading UI when updating state. Transitions keep the previous UI visible while new data is fetching.
529
+
530
+ ### Using startTransition
531
+
532
+ ```tsx
533
+ import { useState, Suspense, startTransition } from "react";
534
+
535
+ function DogSelector() {
536
+ const { data } = useSuspenseQuery(GET_DOGS);
537
+ const [selectedDog, setSelectedDog] = useState(data.dogs[0].id);
538
+
539
+ return (
540
+ <>
541
+ <select
542
+ value={selectedDog}
543
+ onChange={(e) => {
544
+ // Wrap state update in startTransition
545
+ startTransition(() => {
546
+ setSelectedDog(e.target.value);
547
+ });
548
+ }}
549
+ >
550
+ {data.dogs.map((dog) => (
551
+ <option key={dog.id} value={dog.id}>
552
+ {dog.name}
553
+ </option>
554
+ ))}
555
+ </select>
556
+ <Suspense fallback={<div>Loading...</div>}>
557
+ <Dog id={selectedDog} />
558
+ </Suspense>
559
+ </>
560
+ );
561
+ }
562
+ ```
563
+
564
+ ### Using useTransition
565
+
566
+ Use `useTransition` to get an `isPending` flag for visual feedback during transitions.
567
+
568
+ ```tsx
569
+ import { useState, Suspense, useTransition } from "react";
570
+
571
+ function DogSelector() {
572
+ const [isPending, startTransition] = useTransition();
573
+ const { data } = useSuspenseQuery(GET_DOGS);
574
+ const [selectedDog, setSelectedDog] = useState(data.dogs[0].id);
575
+
576
+ return (
577
+ <>
578
+ <select
579
+ style={{ opacity: isPending ? 0.5 : 1 }}
580
+ value={selectedDog}
581
+ onChange={(e) => {
582
+ startTransition(() => {
583
+ setSelectedDog(e.target.value);
584
+ });
585
+ }}
586
+ >
587
+ {data.dogs.map((dog) => (
588
+ <option key={dog.id} value={dog.id}>
589
+ {dog.name}
590
+ </option>
591
+ ))}
592
+ </select>
593
+ <Suspense fallback={<div>Loading...</div>}>
594
+ <Dog id={selectedDog} />
595
+ </Suspense>
596
+ </>
597
+ );
598
+ }
599
+ ```
600
+
601
+ ## Avoiding Request Waterfalls
602
+
603
+ Request waterfalls occur when a child component waits for the parent to finish rendering before it can start fetching its own data. Use `useBackgroundQuery` to start fetching child data earlier in the component tree.
604
+
605
+ > **Note**: When one query depends on the result of another query (e.g., the child query needs an ID from the parent query), the waterfall is unavoidable. The best solution is to restructure your schema to fetch all needed data in a single nested query.
606
+
607
+ ### Example: Independent Queries
608
+
609
+ When queries don't depend on each other, use `useBackgroundQuery` to start them in parallel:
610
+
611
+ ```tsx
612
+ const GET_USER = gql`
613
+ query GetUser($id: String!) {
614
+ user(id: $id) {
615
+ id
616
+ name
617
+ }
618
+ }
619
+ `;
620
+
621
+ const GET_POSTS = gql`
622
+ query GetPosts {
623
+ posts {
624
+ id
625
+ title
626
+ }
627
+ }
628
+ `;
629
+
630
+ function Parent() {
631
+ // Both queries start immediately - no waterfall
632
+ const [userRef] = useBackgroundQuery(GET_USER, {
633
+ variables: { id: "1" },
634
+ });
635
+
636
+ const [postsRef] = useBackgroundQuery(GET_POSTS);
637
+
638
+ return (
639
+ <Suspense fallback={<div>Loading...</div>}>
640
+ <UserProfile queryRef={userRef} />
641
+ <PostsList queryRef={postsRef} />
642
+ </Suspense>
643
+ );
644
+ }
645
+
646
+ function UserProfile({ queryRef }: { queryRef: QueryRef<UserData> }) {
647
+ const { data } = useReadQuery(queryRef);
648
+
649
+ return <div>User: {data.user.name}</div>;
650
+ }
651
+
652
+ function PostsList({ queryRef }: { queryRef: QueryRef<PostsData> }) {
653
+ const { data } = useReadQuery(queryRef);
654
+
655
+ return (
656
+ <ul>
657
+ {data.posts.map((post) => (
658
+ <li key={post.id}>{post.title}</li>
659
+ ))}
660
+ </ul>
661
+ );
662
+ }
663
+ ```
664
+
665
+ ## Fetch Policies
666
+
667
+ Suspense hooks support most of the same fetch policies as `useQuery`, controlling how the query interacts with the cache. Note that `cache-only` and `standby` are not supported by Suspense hooks.
668
+
669
+ | Policy | Description |
670
+ | ------------------- | ---------------------------------------------------------- |
671
+ | `cache-first` | Return cached data if available, otherwise fetch (default) |
672
+ | `cache-and-network` | Return cached data immediately, then fetch and update |
673
+ | `network-only` | Always fetch, update cache, ignore cached data |
674
+ | `no-cache` | Always fetch, never read or write cache |
675
+
676
+ ### Usage Examples
677
+
678
+ ```tsx
679
+ // Always fetch fresh data
680
+ const { data } = useSuspenseQuery(GET_NOTIFICATIONS, {
681
+ fetchPolicy: "network-only",
682
+ });
683
+
684
+ // Prefer cached data
685
+ const { data } = useSuspenseQuery(GET_CATEGORIES, {
686
+ fetchPolicy: "cache-first",
687
+ });
688
+
689
+ // Show cached data while fetching fresh data
690
+ const { data } = useSuspenseQuery(GET_POSTS, {
691
+ fetchPolicy: "cache-and-network",
692
+ });
693
+ ```
694
+
695
+ ## Streaming SSR or React Server Components
696
+
697
+ Apollo Client integrates with modern React frameworks that support Streaming SSR and React Server Components. For detailed setup instructions specific to your framework, see:
698
+
699
+ - **Next.js App Router**: [integration-nextjs.md](./integration-nextjs.md) - Includes React Server Components, PreloadQuery component, and streaming SSR
700
+ - **React Router**: [integration-react-router.md](./integration-react-router.md) - Framework mode with SSR support
701
+ - **TanStack Start**: [integration-tanstack-start.md](./integration-tanstack-start.md) - Full-stack React framework with SSR
702
+
703
+ These guides cover:
704
+
705
+ - Framework-specific client setup and configuration
706
+ - Preloading queries for optimal performance
707
+ - Streaming SSR with `useBackgroundQuery` and Suspense
708
+ - Error handling in server-rendered environments
709
+
710
+ ## Conditional Queries
711
+
712
+ ### Using skipToken
713
+
714
+ Use `skipToken` to conditionally skip queries without TypeScript issues. When `skipToken` is used, the component won't suspend and `data` will be `undefined`.
715
+
716
+ ```tsx
717
+ import { skipToken } from "@apollo/client";
718
+
719
+ const GET_USER = gql`
720
+ query GetUser($id: ID!) {
721
+ user(id: $id) {
722
+ id
723
+ name
724
+ email
725
+ }
726
+ }
727
+ `;
728
+
729
+ function UserProfile({ userId }: { userId: string | null }) {
730
+ const { data, dataState } = useSuspenseQuery(
731
+ GET_USER,
732
+ !userId ? skipToken : (
733
+ {
734
+ variables: { id: userId },
735
+ }
736
+ )
737
+ );
738
+
739
+ if (dataState !== "complete") {
740
+ return <p>Select a user</p>;
741
+ }
742
+
743
+ return <Profile user={data.user} />;
744
+ }
745
+ ```
746
+
747
+ ### Conditional Rendering
748
+
749
+ Alternatively, use conditional rendering to control when Suspense hooks are called. This provides better type safety and clearer component logic.
750
+
751
+ ```tsx
752
+ function UserProfile({ userId }: { userId: string | null }) {
753
+ if (!userId) {
754
+ return <p>Select a user</p>;
755
+ }
756
+
757
+ return (
758
+ <Suspense fallback={<div>Loading...</div>}>
759
+ <UserDetails userId={userId} />
760
+ </Suspense>
761
+ );
762
+ }
763
+
764
+ function UserDetails({ userId }: { userId: string }) {
765
+ const { data } = useSuspenseQuery(GET_USER, {
766
+ variables: { id: userId },
767
+ });
768
+
769
+ return <Profile user={data.user} />;
770
+ }
771
+ ```
772
+
773
+ > **Note**: Using conditional rendering with `skipToken` provides better type safety and avoids issues with required variables. The `skip` option is deprecated in favor of `skipToken`.