@apollo/client 4.1.7 → 4.1.8

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 (27) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/__cjs/react/hooks/useSubscription.cjs +1 -1
  3. package/__cjs/react/hooks/useSubscription.cjs.map +1 -1
  4. package/__cjs/react/hooks/useSubscription.d.cts +2 -2
  5. package/__cjs/version.cjs +1 -1
  6. package/package.json +3 -2
  7. package/react/hooks/useSubscription.d.ts +2 -2
  8. package/react/hooks/useSubscription.js +1 -1
  9. package/react/hooks/useSubscription.js.map +1 -1
  10. package/react/hooks-compiled/useSubscription.d.ts +2 -2
  11. package/react/hooks-compiled/useSubscription.js +1 -1
  12. package/react/hooks-compiled/useSubscription.js.map +1 -1
  13. package/skills/apollo-client/SKILL.md +168 -0
  14. package/skills/apollo-client/references/caching.md +560 -0
  15. package/skills/apollo-client/references/error-handling.md +344 -0
  16. package/skills/apollo-client/references/fragments.md +804 -0
  17. package/skills/apollo-client/references/integration-client.md +340 -0
  18. package/skills/apollo-client/references/integration-nextjs.md +325 -0
  19. package/skills/apollo-client/references/integration-react-router.md +256 -0
  20. package/skills/apollo-client/references/integration-tanstack-start.md +378 -0
  21. package/skills/apollo-client/references/mutations.md +549 -0
  22. package/skills/apollo-client/references/queries.md +416 -0
  23. package/skills/apollo-client/references/state-management.md +428 -0
  24. package/skills/apollo-client/references/suspense-hooks.md +773 -0
  25. package/skills/apollo-client/references/troubleshooting.md +487 -0
  26. package/skills/apollo-client/references/typescript-codegen.md +133 -0
  27. package/version.js +1 -1
@@ -0,0 +1,428 @@
1
+ # State Management Reference
2
+
3
+ ## Table of Contents
4
+
5
+ - [Reactive Variables](#reactive-variables)
6
+ - [Local-Only Fields](#local-only-fields)
7
+ - [Local Field Read Functions (Type Policies)](#local-field-read-functions-type-policies)
8
+ - [Combining Remote and Local State](#combining-remote-and-local-state)
9
+ - [useReactiveVar Hook](#usereactivevar-hook)
10
+
11
+ ## Reactive Variables
12
+
13
+ Reactive variables are a way to store local state outside of the Apollo Client cache while still triggering reactive updates.
14
+
15
+ **Important**: Reactive variables store a single value that notifies `ApolloClient` instances when changed. They do not have separate values per ApolloClient instance. In multi-user environments like SSR, global or module-level reactive variables could be shared between users and cause data leaks. In frameworks that use SSR, always avoid storing reactive variables as globals.
16
+
17
+ ### Creating Reactive Variables
18
+
19
+ ```typescript
20
+ import { makeVar } from "@apollo/client";
21
+
22
+ // Simple reactive variable
23
+ export const isLoggedInVar = makeVar<boolean>(false);
24
+
25
+ // Object reactive variable
26
+ export const cartItemsVar = makeVar<CartItem[]>([]);
27
+
28
+ // Complex state
29
+ interface AppState {
30
+ theme: "light" | "dark";
31
+ sidebarOpen: boolean;
32
+ notifications: Notification[];
33
+ }
34
+
35
+ export const appStateVar = makeVar<AppState>({
36
+ theme: "light",
37
+ sidebarOpen: true,
38
+ notifications: [],
39
+ });
40
+ ```
41
+
42
+ ### Reading Reactive Variables
43
+
44
+ ```tsx
45
+ // Direct read (non-reactive)
46
+ const isLoggedIn = isLoggedInVar();
47
+
48
+ // Reactive read in component
49
+ import { useReactiveVar } from "@apollo/client/react";
50
+
51
+ function AuthButton() {
52
+ const isLoggedIn = useReactiveVar(isLoggedInVar);
53
+
54
+ return isLoggedIn ?
55
+ <button onClick={() => isLoggedInVar(false)}>Logout</button>
56
+ : <button onClick={() => isLoggedInVar(true)}>Login</button>;
57
+ }
58
+ ```
59
+
60
+ ### Updating Reactive Variables
61
+
62
+ ```typescript
63
+ // Set new value
64
+ isLoggedInVar(true);
65
+
66
+ // Update based on current value
67
+ cartItemsVar([...cartItemsVar(), newItem]);
68
+
69
+ // Update object state
70
+ appStateVar({
71
+ ...appStateVar(),
72
+ theme: "dark",
73
+ });
74
+
75
+ // Helper function pattern
76
+ export function toggleSidebar() {
77
+ const current = appStateVar();
78
+ appStateVar({ ...current, sidebarOpen: !current.sidebarOpen });
79
+ }
80
+
81
+ export function addNotification(notification: Notification) {
82
+ const current = appStateVar();
83
+ appStateVar({
84
+ ...current,
85
+ notifications: [...current.notifications, notification],
86
+ });
87
+ }
88
+ ```
89
+
90
+ ## Local-Only Fields
91
+
92
+ Local-only fields are fields defined in queries but resolved entirely on the client using the `@client` directive.
93
+
94
+ **Important**: To use any `@client` fields, you need to add `LocalState` to the `ApolloClient` initialization:
95
+
96
+ ```typescript
97
+ import { ApolloClient, InMemoryCache } from "@apollo/client";
98
+ import { LocalState } from "@apollo/client/local-state";
99
+
100
+ const client = new ApolloClient({
101
+ cache: new InMemoryCache(),
102
+ localState: new LocalState({}),
103
+ // ... other options
104
+ });
105
+ ```
106
+
107
+ > **Note**: `LocalState` is an Apollo Client 4.x concept and did not exist as a class in previous versions. In previous versions, a `localState` option was not necessary, and local resolvers (if used) could be passed directly to the `ApolloClient` constructor.
108
+
109
+ ### Basic @client Fields
110
+
111
+ ```tsx
112
+ const GET_USER_WITH_LOCAL = gql`
113
+ query GetUserWithLocal($id: ID!) {
114
+ user(id: $id) {
115
+ id
116
+ name
117
+ email
118
+ # Local-only fields
119
+ isSelected @client
120
+ displayName @client
121
+ }
122
+ }
123
+ `;
124
+
125
+ function UserCard({ userId }: { userId: string }) {
126
+ const { data } = useQuery(GET_USER_WITH_LOCAL, {
127
+ variables: { id: userId },
128
+ });
129
+
130
+ return (
131
+ <div className={data?.user.isSelected ? "selected" : ""}>
132
+ <h2>{data?.user.displayName}</h2>
133
+ <p>{data?.user.email}</p>
134
+ </div>
135
+ );
136
+ }
137
+ ```
138
+
139
+ ### Local Field Read Functions (Type Policies)
140
+
141
+ Local field `read` functions are defined in entity-level type policies. You can use reactive variables inside these `read` functions, along with other calculations or derived values:
142
+
143
+ ```typescript
144
+ const cache = new InMemoryCache({
145
+ typePolicies: {
146
+ User: {
147
+ fields: {
148
+ // Simple local field from reactive variable
149
+ isSelected: {
150
+ read(_, { readField }) {
151
+ const id = readField("id");
152
+ return selectedUsersVar().includes(id);
153
+ },
154
+ },
155
+
156
+ // Computed local field (derived value)
157
+ displayName: {
158
+ read(_, { readField }) {
159
+ const name = readField("name");
160
+ const email = readField("email");
161
+ return name || email?.split("@")[0] || "Anonymous";
162
+ },
163
+ },
164
+ },
165
+ },
166
+ },
167
+ });
168
+ ```
169
+
170
+ ## LocalState Resolvers
171
+
172
+ ### Query-Level Local Resolvers
173
+
174
+ Query-level local fields can be defined using `LocalState` resolvers. **Note**: Do not read reactive variables inside LocalState resolvers - this is not a documented/tested feature. It might not behave as expected.
175
+
176
+ ```typescript
177
+ import { LocalState } from "@apollo/client/local-state";
178
+
179
+ const client = new ApolloClient({
180
+ cache: new InMemoryCache(),
181
+ localState: new LocalState({
182
+ resolvers: {
183
+ Query: {
184
+ // Read from localStorage
185
+ theme: () => {
186
+ if (typeof window !== "undefined") {
187
+ return localStorage.getItem("theme") || "light";
188
+ }
189
+ return "light";
190
+ },
191
+
192
+ // Read from cache
193
+ currentUser: (_, __, { cache }) => {
194
+ if (typeof window === "undefined") return null;
195
+ const userId = localStorage.getItem("currentUserId");
196
+ if (!userId) return null;
197
+ return cache.readFragment({
198
+ id: cache.identify({ __typename: "User", id: userId }),
199
+ fragment: gql`
200
+ fragment CurrentUser on User {
201
+ id
202
+ name
203
+ email
204
+ }
205
+ `,
206
+ });
207
+ },
208
+
209
+ // Compute value
210
+ isOnline: () => {
211
+ if (typeof navigator !== "undefined") {
212
+ return navigator.onLine;
213
+ }
214
+ return true;
215
+ },
216
+ },
217
+ },
218
+ }),
219
+ });
220
+ ```
221
+
222
+ ### Using Local Query Fields
223
+
224
+ ```tsx
225
+ const GET_AUTH_STATE = gql`
226
+ query GetAuthState {
227
+ isLoggedIn @client
228
+ currentUser @client {
229
+ id
230
+ name
231
+ email
232
+ }
233
+ }
234
+ `;
235
+
236
+ function AuthStatus() {
237
+ const { data } = useQuery(GET_AUTH_STATE);
238
+
239
+ if (!data?.isLoggedIn) {
240
+ return <LoginButton />;
241
+ }
242
+
243
+ return <UserMenu user={data.currentUser} />;
244
+ }
245
+ ```
246
+
247
+ ## Combining Remote and Local State
248
+
249
+ ### Mixing Remote and Local Fields
250
+
251
+ ```tsx
252
+ const GET_PRODUCTS = gql`
253
+ query GetProducts {
254
+ products {
255
+ id
256
+ name
257
+ price
258
+ # Local fields
259
+ quantity @client
260
+ isInCart @client
261
+ }
262
+ }
263
+ `;
264
+
265
+ const cache = new InMemoryCache({
266
+ typePolicies: {
267
+ Product: {
268
+ fields: {
269
+ quantity: {
270
+ read(_, { readField }) {
271
+ const id = readField("id");
272
+ const cartItem = cartItemsVar().find(
273
+ (item) => item.productId === id
274
+ );
275
+ return cartItem?.quantity ?? 0;
276
+ },
277
+ },
278
+
279
+ isInCart: {
280
+ read(_, { readField }) {
281
+ const id = readField("id");
282
+ return cartItemsVar().some((item) => item.productId === id);
283
+ },
284
+ },
285
+ },
286
+ },
287
+ },
288
+ });
289
+ ```
290
+
291
+ ### Local Mutations
292
+
293
+ ```tsx
294
+ import { LocalState } from "@apollo/client/local-state";
295
+
296
+ const client = new ApolloClient({
297
+ cache: new InMemoryCache(),
298
+ localState: new LocalState({
299
+ resolvers: {
300
+ Mutation: {
301
+ addToCart: (_, { productId, quantity }, { cache }) => {
302
+ // Read current cart from cache
303
+ const { cart } = cache.readQuery({ query: GET_CART }) || { cart: [] };
304
+
305
+ const existing = cart.find((item) => item.productId === productId);
306
+
307
+ const updatedCart =
308
+ existing ?
309
+ cart.map((item) =>
310
+ item.productId === productId ?
311
+ { ...item, quantity: item.quantity + quantity }
312
+ : item
313
+ )
314
+ : [...cart, { productId, quantity, __typename: "CartItem" }];
315
+
316
+ // Write updated cart back to cache
317
+ cache.writeQuery({
318
+ query: GET_CART,
319
+ data: { cart: updatedCart },
320
+ });
321
+
322
+ return true;
323
+ },
324
+ },
325
+ },
326
+ }),
327
+ });
328
+
329
+ const ADD_TO_CART = gql`
330
+ mutation AddToCart($productId: ID!, $quantity: Int!) {
331
+ addToCart(productId: $productId, quantity: $quantity) @client
332
+ }
333
+ `;
334
+ ```
335
+
336
+ ### Persisting Local State
337
+
338
+ ```typescript
339
+ // Create a helper function to permanently subscribe to reactive variable changes, without creating memory leaks
340
+ function subscribeToVariable<T>(
341
+ weakRef: WeakRef<ReactiveVar<T>>,
342
+ listener: ReactiveListener<T>
343
+ ) {
344
+ weakRef.deref()?.onNextChange((value) => {
345
+ listener(value);
346
+ subscribeToVariable(weakRef, listener);
347
+ });
348
+ }
349
+
350
+ // Create reactive variable with persistence
351
+ const persistentCartVar = makeVar<CartItem[]>(
352
+ typeof window !== "undefined" && localStorage.getItem("cart") ?
353
+ JSON.parse(localStorage.getItem("cart")!)
354
+ : []
355
+ );
356
+
357
+ // Save to localStorage when reactive variable changes
358
+ subscribeToVariable(new WeakRef(persistentCartVar), (items) => {
359
+ try {
360
+ if (typeof window !== "undefined") {
361
+ localStorage.setItem("cart", JSON.stringify(items));
362
+ }
363
+ } catch (error) {
364
+ console.error("Failed to persist cart:", error);
365
+ }
366
+ });
367
+ ```
368
+
369
+ ## useReactiveVar Hook
370
+
371
+ The `useReactiveVar` hook subscribes a component to reactive variable updates.
372
+
373
+ ### Basic Usage
374
+
375
+ ```tsx
376
+ import { useReactiveVar } from "@apollo/client/react";
377
+
378
+ function ThemeToggle() {
379
+ const theme = useReactiveVar(themeVar);
380
+
381
+ return (
382
+ <button onClick={() => themeVar(theme === "light" ? "dark" : "light")}>
383
+ Current: {theme}
384
+ </button>
385
+ );
386
+ }
387
+ ```
388
+
389
+ ### With Derived State
390
+
391
+ ```tsx
392
+ function CartSummary() {
393
+ const cartItems = useReactiveVar(cartItemsVar);
394
+
395
+ // Derived values are computed on each render
396
+ const totalItems = cartItems.reduce((sum, item) => sum + item.quantity, 0);
397
+ const totalPrice = cartItems.reduce(
398
+ (sum, item) => sum + item.price * item.quantity,
399
+ 0
400
+ );
401
+
402
+ return (
403
+ <div>
404
+ <p>Items: {totalItems}</p>
405
+ <p>Total: ${totalPrice.toFixed(2)}</p>
406
+ </div>
407
+ );
408
+ }
409
+ ```
410
+
411
+ ### Multiple Reactive Variables
412
+
413
+ ```tsx
414
+ function AppLayout() {
415
+ const theme = useReactiveVar(themeVar);
416
+ const sidebarOpen = useReactiveVar(sidebarOpenVar);
417
+ const isLoggedIn = useReactiveVar(isLoggedInVar);
418
+
419
+ return (
420
+ <div className={`app ${theme}`}>
421
+ {isLoggedIn && sidebarOpen && <Sidebar />}
422
+ <main>
423
+ <Outlet />
424
+ </main>
425
+ </div>
426
+ );
427
+ }
428
+ ```