Setting the file. One moment.
Subchapter 1.1
references/caching.mdMarkdown11 KBView on GitHub
import { InMemoryCache } from "@apollo/client";
const cache = new InMemoryCache({
// Custom type policies
typePolicies: {
Query: {
fields: {
// Query-level field policies
},
},
User: {
keyFields: ["id"],
fields: {
// User-level field policies
},
},
},
// Custom type name handling (rare)
possibleTypes: {
Character: ["Human", "Droid"],
Node: ["User", "Post", "Comment"],
},
});new InMemoryCache({
// Define how types are identified in cache
typePolicies: {
/* ... */
},
// Interface/union type mappings between supertypes and their subtypes
possibleTypes: {
/* ... */
},
// Custom function to generate cache IDs (rare)
dataIdFromObject: (object) => {
if (object.__typename === "Book") {
return `Book:${object.isbn}`;
}
return defaultDataIdFromObject(object);
},
});Apollo Client normalizes data by splitting query results into individual objects and storing them by unique identifier.
# Query
query GetPost {
post(id: "1") {
id
title
author {
id
name
}
}
}// Normalized cache structure
{
'Post:1': {
__typename: 'Post',
id: '1',
title: 'Hello World',
author: { __ref: 'User:42' }
},
'User:42': {
__typename: 'User',
id: '42',
name: 'John'
},
ROOT_QUERY: {
'post({"id":"1"})': { __ref: 'Post:1' }
}
}User:42 is updated anywhere, all components showing that user updateCustomize how objects are identified in the cache.
const cache = new InMemoryCache({
typePolicies: {
// Use ISBN instead of id for books
Book: {
keyFields: ["isbn"],
},
// Composite key
UserSession: {
keyFields: ["userId", "deviceId"],
},
// Nested key
Review: {
keyFields: ["book", ["isbn"], "reviewer", ["id"]],
},
// No key fields (singleton, only one object in cache per type)
AppSettings: {
keyFields: [],
},
// Disable normalization (objects of this type will be stored with their
// parent entity. The same object might end up multiple times in the cache
// and run out of sync. Use with caution, only if this object really relates
// to a property of their parent entity and cannot exist on its own.)
Address: {
keyFields: false,
},
},
});Control how incoming data merges with existing data.
const cache = new InMemoryCache({
typePolicies: {
User: {
fields: {
// Deep merge profile object
profile: {
merge: true, // Shorthand for deep merge
},
// Custom merge logic
notifications: {
merge(existing = [], incoming, { mergeObjects }) {
// Prepend new notifications
return [...incoming, ...existing];
},
},
},
},
},
});Transform cached data when reading.
const cache = new InMemoryCache({
typePolicies: {
User: {
fields: {
// Computed field
fullName: {
read(_, { readField }) {
const firstName = readField("firstName");
const lastName = readField("lastName");
return `${firstName} ${lastName}`;
},
},
// Transform existing field
birthDate: {
read(existing) {
return existing ? new Date(existing) : null;
},
},
// Default value
role: {
read(existing = "USER") {
return existing;
},
},
},
},
},
});Control how incoming data is stored.
const cache = new InMemoryCache({
typePolicies: {
User: {
fields: {
// Accumulate items instead of replacing
friends: {
merge(existing = [], incoming) {
return [...existing, ...incoming];
},
},
// Merge objects deeply
settings: {
merge(existing, incoming, { mergeObjects }) {
return mergeObjects(existing, incoming);
},
},
},
},
Query: {
fields: {
// Merge paginated results
posts: {
keyArgs: ["category"], // Only category affects cache key
merge(existing = { items: [] }, incoming) {
return {
...incoming,
items: [...existing.items, ...incoming.items],
};
},
},
},
},
},
});Control which arguments affect cache storage.
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
// Different cache entry per userId only
// (limit, offset don't create new entries)
userPosts: {
keyArgs: ["userId"],
},
// No arguments affect cache key
// (useful for pagination)
feed: {
keyArgs: false,
},
// Nested argument
search: {
keyArgs: ["filter", ["category", "status"]],
},
},
},
},
});import { offsetLimitPagination } from "@apollo/client/utilities";
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
posts: offsetLimitPagination(),
// With key arguments
userPosts: offsetLimitPagination(["userId"]),
},
},
},
});import { relayStylePagination } from "@apollo/client/utilities";
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
posts: relayStylePagination(),
// With key arguments
userPosts: relayStylePagination(["userId"]),
},
},
},
});const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
feed: {
keyArgs: false,
merge(existing, incoming, { args }) {
const merged = existing ? existing.slice(0) : [];
const offset = args?.offset ?? 0;
for (let i = 0; i < incoming.length; i++) {
merged[offset + i] = incoming[i];
}
return merged;
},
read(existing, { args }) {
const offset = args?.offset ?? 0;
const limit = args?.limit ?? existing?.length ?? 0;
return existing?.slice(offset, offset + limit);
},
},
},
},
},
});function PostList() {
const { data, fetchMore, loading } = useQuery(GET_POSTS, {
variables: { offset: 0, limit: 10 },
});
const loadMore = () => {
fetchMore({
variables: {
offset: data.posts.length,
},
// With proper type policies, no updateQuery needed
});
};
return (
<div>
{data?.posts.map((post) => <PostCard key={post.id} post={post} />)}
<button onClick={loadMore} disabled={loading}>
Load More
</button>
</div>
);
}// Read data from cache
const data = cache.readQuery({
query: GET_TODOS,
});
// With variables
const userData = cache.readQuery({
query: GET_USER,
variables: { id: "1" },
});// Write data to cache
cache.writeQuery({
query: GET_TODOS,
data: {
todos: [
{ __typename: "Todo", id: "1", text: "Buy milk", completed: false },
],
},
});
// With variables
cache.writeQuery({
query: GET_USER,
variables: { id: "1" },
data: {
user: { __typename: "User", id: "1", name: "John" },
},
});// Read a specific object - use cache.identify for safety
const user = cache.readFragment({
id: cache.identify({ __typename: "User", id: "1" }),
fragment: gql`
fragment UserFragment on User {
id
name
email
}
`,
});
// Apollo Client 4.1+: Use 'from' parameter (recommended)
const user = cache.readFragment({
from: { __typename: "User", id: "1" },
fragment: gql`
fragment UserFragment on User {
id
name
email
}
`,
});
// Update a specific object
cache.writeFragment({
id: cache.identify({ __typename: "User", id: "1" }),
fragment: gql`
fragment UpdateUser on User {
name
}
`,
data: {
name: "Jane",
},
});
// Apollo Client 4.1+: Use 'from' parameter (recommended)
cache.writeFragment({
from: { __typename: "User", id: "1" },
fragment: gql`
fragment UpdateUser on User {
name
}
`,
data: {
name: "Jane",
},
});// Modify fields directly
cache.modify({
id: cache.identify(user),
fields: {
// Set new value
name: () => "New Name",
// Transform existing value
postCount: (existing) => existing + 1,
// Delete field
temporaryField: (_, { DELETE }) => DELETE,
// Add to array
friends: (existing, { toReference }) => [
...existing,
toReference({ __typename: "User", id: "2" }),
],
},
});// Remove object from cache
cache.evict({ id: "User:1" });
// Remove specific field
cache.evict({ id: "User:1", fieldName: "friends" });
// Remove with broadcast (trigger re-renders)
cache.evict({ id: "User:1", broadcast: true });// After evicting objects, clean up dangling references
cache.evict({ id: "User:1" });
cache.gc();// Prevent objects from being garbage collected
const release = cache.retain("User:1");
// Later, allow GC
release();
cache.gc();// Get all cached data
const cacheContents = cache.extract();
// Restore cache state
cache.restore(previousCacheContents);
// Get identified object cache key
const userId = cache.identify({ __typename: "User", id: "1" });
// Returns: 'User:1'