Skip to content

Commit b639e61

Browse files
bas0Nphryneas
andauthored
docs: add documentation for cache.batch method (apollographql#12990) (apollographql#13048)
## docs: add documentation for `cache.batch` method Fixes apollographql#12990 Adds comprehensive documentation for the `cache.batch` method: - **JSDoc** (`src/cache/core/cache.ts`) - Added doc block with description, examples, and parameter docs - **Guide** (`docs/source/caching/cache-interaction.mdx`) - New section covering usage, optimistic updates, and `onWatchUpdated` - **API Reference** (`docs/source/api/cache/InMemoryCache.mdx`) - Full API docs with options table and signature Co-authored-by: Lenz Weber-Tronic <[email protected]>
1 parent a90122d commit b639e61

File tree

7 files changed

+214
-35
lines changed

7 files changed

+214
-35
lines changed

.api-reports/api-report-cache.api.md

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ export namespace ApolloCache {
5959
export abstract class ApolloCache {
6060
// (undocumented)
6161
readonly assumeImmutableResults: boolean;
62-
// (undocumented)
6362
batch<U>(options: Cache_2.BatchOptions<this, U>): U;
6463
abstract diff<TData = unknown, TVariables extends OperationVariables = OperationVariables>(query: Cache_2.DiffOptions<TData, TVariables>): Cache_2.DiffResult<TData>;
6564
// (undocumented)
@@ -124,13 +123,9 @@ type BroadcastOptions = Pick<Cache_2.BatchOptions<InMemoryCache>, "optimistic" |
124123
namespace Cache_2 {
125124
// (undocumented)
126125
interface BatchOptions<TCache extends ApolloCache, TUpdateResult = void> {
127-
// (undocumented)
128126
onWatchUpdated?: (this: TCache, watch: Cache_2.WatchOptions, diff: Cache_2.DiffResult<any>, lastDiff?: Cache_2.DiffResult<any> | undefined) => any;
129-
// (undocumented)
130127
optimistic?: string | boolean;
131-
// (undocumented)
132128
removeOptimistic?: string;
133-
// (undocumented)
134129
update(cache: TCache): TUpdateResult;
135130
}
136131
// (undocumented)
@@ -497,7 +492,6 @@ export class InMemoryCache extends ApolloCache {
497492
constructor(config?: InMemoryCacheConfig);
498493
// (undocumented)
499494
readonly assumeImmutableResults = true;
500-
// (undocumented)
501495
batch<TUpdateResult>(options: Cache_2.BatchOptions<InMemoryCache, TUpdateResult>): TUpdateResult;
502496
// Warning: (ae-forgotten-export) The symbol "BroadcastOptions" needs to be exported by the entry point index.d.ts
503497
//

.api-reports/api-report.api.md

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ export namespace ApolloCache {
5858
export abstract class ApolloCache {
5959
// (undocumented)
6060
readonly assumeImmutableResults: boolean;
61-
// (undocumented)
6261
batch<U>(options: Cache_2.BatchOptions<this, U>): U;
6362
abstract diff<TData = unknown, TVariables extends OperationVariables = OperationVariables>(query: Cache_2.DiffOptions<TData, TVariables>): Cache_2.DiffResult<TData>;
6463
// (undocumented)
@@ -551,13 +550,9 @@ export const build: "source" | "esm" | "cjs";
551550
namespace Cache_2 {
552551
// (undocumented)
553552
interface BatchOptions<TCache extends ApolloCache, TUpdateResult = void> {
554-
// (undocumented)
555553
onWatchUpdated?: (this: TCache, watch: Cache_2.WatchOptions, diff: Cache_2.DiffResult<any>, lastDiff?: Cache_2.DiffResult<any> | undefined) => any;
556-
// (undocumented)
557554
optimistic?: string | boolean;
558-
// (undocumented)
559555
removeOptimistic?: string;
560-
// (undocumented)
561556
update(cache: TCache): TUpdateResult;
562557
}
563558
// (undocumented)
@@ -1325,7 +1320,6 @@ export class InMemoryCache extends ApolloCache {
13251320
constructor(config?: InMemoryCacheConfig);
13261321
// (undocumented)
13271322
readonly assumeImmutableResults = true;
1328-
// (undocumented)
13291323
batch<TUpdateResult>(options: Cache_2.BatchOptions<InMemoryCache, TUpdateResult>): TUpdateResult;
13301324
// Warning: (ae-forgotten-export) The symbol "BroadcastOptions" needs to be exported by the entry point index.d.ts
13311325
//

docs/source/api/cache/InMemoryCache.mdx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -940,6 +940,12 @@ public updateFragment<TData = unknown, TVariables = OperationVariables>(
940940
): TData | null
941941
```
942942

943+
<FunctionDetails
944+
canonicalReference="@apollo/client!InMemoryCache#batch:member(1)"
945+
headingLevel={2}
946+
result={false}
947+
/>
948+
943949
## `identify`
944950

945951
Returns the canonical ID for a specified cached object.

docs/source/caching/cache-interaction.mdx

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Apollo Client supports multiple strategies for interacting with cached data:
1313
| [Using GraphQL queries](#using-graphql-queries) | `readQuery` / `writeQuery` / `updateQuery` | Use standard GraphQL queries for managing both remote and local data. |
1414
| [Using GraphQL fragments](#using-graphql-fragments) | `readFragment` / `writeFragment` / `updateFragment` / `useFragment` | Access the fields of any cached object without composing an entire query to reach that object. |
1515
| [Directly modifying cached fields](#using-cachemodify) | `cache.modify` | Manipulate cached data without using GraphQL at all. |
16+
| [Batching cache operations](#using-cachebatch) | `cache.batch` | Group multiple cache operations into a single transaction for better performance. |
1617

1718
You can use whichever combination of strategies and methods are most helpful for your use case.
1819

@@ -302,6 +303,135 @@ The update function's return value is passed to either `writeQuery` or `writeFra
302303

303304
> See the full API reference for [`cache.updateQuery`](../api/cache/InMemoryCache/#updatequery) and [`cache.updateFragment`](../api/cache/InMemoryCache/#updatefragment).
304305
306+
### Using `cache.batch`
307+
308+
Use `cache.batch` to group multiple cache operations into a single transaction. This provides two key benefits:
309+
310+
- **Performance**: Watchers (and thereby React components) are notified only once after all operations complete, rather than after each individual operation.
311+
- **Control**: You gain fine-grained control over optimistic updates and can intercept watcher notifications.
312+
313+
Here's an example that updates multiple cached items at once:
314+
315+
```js
316+
cache.batch({
317+
update(cache) {
318+
// All these operations happen in a single batch
319+
cache.writeQuery({
320+
query: GET_TODOS,
321+
data: { todos: updatedTodos },
322+
});
323+
324+
cache.modify({
325+
id: cache.identify(currentUser),
326+
fields: {
327+
todoCount(existing) {
328+
return existing + 1;
329+
},
330+
},
331+
});
332+
333+
// Cleanup after the updates
334+
cache.evict({ id: "Todo:old-item" });
335+
},
336+
});
337+
```
338+
339+
#### Working with optimistic updates
340+
341+
The `optimistic` option controls how `batch` handles optimistic data:
342+
343+
```js
344+
// Create a new optimistic layer with a specific ID
345+
cache.batch({
346+
optimistic: "add-todo-123",
347+
update(cache) {
348+
cache.modify({
349+
fields: {
350+
todos(existing = []) {
351+
const newTodoRef = cache.writeFragment({
352+
data: optimisticTodo,
353+
fragment: gql`
354+
fragment NewTodo on Todo {
355+
id
356+
text
357+
completed
358+
}
359+
`,
360+
});
361+
return [...existing, newTodoRef];
362+
},
363+
},
364+
});
365+
},
366+
});
367+
368+
// Later, remove the optimistic layer when the server responds
369+
cache.batch({
370+
update(cache) {
371+
// Write the real server data
372+
cache.writeQuery({
373+
query: GET_TODOS,
374+
data: serverResponse.data,
375+
});
376+
},
377+
// Remove the optimistic layer in the same batch
378+
removeOptimistic: "add-todo-123",
379+
});
380+
```
381+
382+
The `optimistic` option accepts three types of values:
383+
384+
| Value | Behavior |
385+
| ---------------- | -------------------------------------------------------------------------------------------------- |
386+
| `string` | Creates a new optimistic layer with the given ID. Use this to remove these specific changes later. |
387+
| `true` (default) | Updates happen on the current top layer of the cache. |
388+
| `false` | Updates apply only to the root (non-optimistic) cache data, ignoring any optimistic layers. |
389+
390+
#### Intercepting watcher notifications
391+
392+
Use the `onWatchUpdated` callback to inspect or prevent notifications to specific watchers:
393+
394+
```js
395+
cache.batch({
396+
update(cache) {
397+
cache.writeQuery({
398+
query: GET_USER,
399+
data: { user: updatedUser },
400+
});
401+
},
402+
onWatchUpdated(watch, diff, lastDiff) {
403+
console.log("Query affected:", watch.query);
404+
console.log("New result:", diff.result);
405+
406+
// Return false to prevent notifying this watcher
407+
if (shouldSkipUpdate(watch)) {
408+
return false;
409+
}
410+
},
411+
});
412+
```
413+
414+
#### Return values
415+
416+
The `batch` method returns whatever value your `update` function returns:
417+
418+
```js
419+
const result = cache.batch({
420+
update(cache) {
421+
const before = cache.readQuery({ query: GET_COUNT });
422+
cache.writeQuery({
423+
query: GET_COUNT,
424+
data: { count: before.count + 1 },
425+
});
426+
return before.count; // Return the previous count
427+
},
428+
});
429+
430+
console.log("Previous count was:", result);
431+
```
432+
433+
> See the full API reference for [`cache.batch`](../api/cache/InMemoryCache/#batch).
434+
305435
## Using `cache.modify`
306436

307437
The `modify` method of `InMemoryCache` enables you to directly modify the values of individual cached fields, or even delete fields entirely.

src/cache/core/cache.ts

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -180,11 +180,53 @@ export abstract class ApolloCache {
180180

181181
// Transactional API
182182

183-
// The batch method is intended to replace/subsume both performTransaction
184-
// and recordOptimisticTransaction, but performTransaction came first, so we
185-
// provide a default batch implementation that's just another way of calling
186-
// performTransaction. Subclasses of ApolloCache (such as InMemoryCache) can
187-
// override the batch method to do more interesting things with its options.
183+
/**
184+
* Executes multiple cache operations as a single batch, ensuring that
185+
* watchers are only notified once after all operations complete. This is
186+
* useful for improving performance when making multiple cache updates, as it
187+
* prevents unnecessary re-renders or query refetches between individual
188+
* operations.
189+
*
190+
* The `batch` method supports both optimistic and non-optimistic updates, and
191+
* provides fine-grained control over which cache layer receives the updates
192+
* and when watchers are notified.
193+
*
194+
* For usage instructions, see [Interacting with cached data: `cache.batch`](https://www.apollographql.com/docs/react/caching/cache-interaction#using-cachebatch).
195+
*
196+
* @example
197+
*
198+
* ```js
199+
* cache.batch({
200+
* update(cache) {
201+
* cache.writeQuery({
202+
* query: GET_TODOS,
203+
* data: { todos: updatedTodos },
204+
* });
205+
* cache.evict({ id: "Todo:123" });
206+
* },
207+
* });
208+
* ```
209+
*
210+
* @example
211+
*
212+
* ```js
213+
* // Optimistic update with a custom layer ID
214+
* cache.batch({
215+
* optimistic: "add-todo-optimistic",
216+
* update(cache) {
217+
* cache.modify({
218+
* fields: {
219+
* todos(existing = []) {
220+
* return [...existing, newTodoRef];
221+
* },
222+
* },
223+
* });
224+
* },
225+
* });
226+
* ```
227+
*
228+
* @returns The return value of the `update` function.
229+
*/
188230
public batch<U>(options: Cache.BatchOptions<this, U>): U {
189231
const optimisticId =
190232
typeof options.optimistic === "string" ? options.optimistic

src/cache/core/types/Cache.ts

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -124,30 +124,40 @@ export declare namespace Cache {
124124
TCache extends ApolloCache,
125125
TUpdateResult = void,
126126
> {
127-
// Same as the first parameter of performTransaction, except the cache
128-
// argument will have the subclass type rather than ApolloCache.
127+
/**
128+
* A function that performs cache operations. Receives the cache instance as its argument.
129+
*
130+
* The return value of this function becomes the return value of `batch`.
131+
*/
129132
update(cache: TCache): TUpdateResult;
130133

131-
// Passing a string for this option creates a new optimistic layer, with the
132-
// given string as its layer.id, just like passing a string for the
133-
// optimisticId parameter of performTransaction. Passing true is the same as
134-
// passing undefined to performTransaction (running the batch operation
135-
// against the current top layer of the cache), and passing false is the
136-
// same as passing null (running the operation against root/non-optimistic
137-
// cache data).
134+
/**
135+
* Controls how optimistic data is handled:
136+
*
137+
* - `string`: Creates a new optimistic layer with this ID. Use `removeOptimistic` later to remove it.
138+
* - `true`: Updates the current top layer of the cache (including any optimistic data).
139+
* - `false`: Updates only the root (non-optimistic) cache data.
140+
*
141+
* @defaultValue false
142+
*/
138143
optimistic?: string | boolean;
139144

140-
// If you specify the ID of an optimistic layer using this option, that
141-
// layer will be removed as part of the batch transaction, triggering at
142-
// most one broadcast for both the transaction and the removal of the layer.
143-
// Note: this option is needed because calling cache.removeOptimistic during
144-
// the transaction function may not be not safe, since any modifications to
145-
// cache layers may be discarded after the transaction finishes.
145+
/**
146+
* If provided, removes the optimistic layer with this ID after the batch completes.
147+
*
148+
* This is useful for atomically applying server data while removing a pending optimistic update, triggering at most one broadcast for both operations.
149+
*
150+
* Note: this option is needed because calling `cache.removeOptimistic` during the transaction function may not be safe, since any modifications to cache layers may be discarded after the transaction finishes.
151+
*/
146152
removeOptimistic?: string;
147153

148-
// If you want to find out which watched queries were invalidated during
149-
// this batch operation, pass this optional callback function. Returning
150-
// false from the callback will prevent broadcasting this result.
154+
/**
155+
* Optional callback invoked for each watcher affected by the batch operation.
156+
*
157+
* Receives the watch options, the new diff result, and optionally the previous diff result.
158+
*
159+
* Return `false` to prevent broadcasting to that specific watcher.
160+
*/
151161
onWatchUpdated?: (
152162
this: TCache,
153163
watch: Cache.WatchOptions,

src/cache/inmemory/inMemoryCache.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,9 @@ export class InMemoryCache extends ApolloCache {
402402

403403
private txCount = 0;
404404

405+
/**
406+
* {@inheritDoc @apollo/client/cache!ApolloCache#batch:member(1)}
407+
*/
405408
public batch<TUpdateResult>(
406409
options: Cache.BatchOptions<InMemoryCache, TUpdateResult>
407410
): TUpdateResult {

0 commit comments

Comments
 (0)