Configuring the Apollo Client cache
This article describes cache setup and configuration. To learn how to interact with cached data, see Reading and writing data to the cache.
Installation
As of Apollo Client 3.0, the InMemoryCache
class is provided by the @apollo/client
package. No additional libraries are required.
Initialization
Create an InMemoryCache
object and provide it to the ApolloClient
constructor, like so:
import { InMemoryCache, ApolloClient } from '@apollo/client';
const client = new ApolloClient({
// ...other arguments...
cache: new InMemoryCache(options)
});
The InMemoryCache
constructor accepts a variety of configuration options.
Configuration options
Although the cache's default behavior is suitable for a wide variety of applications, you can configure its behavior to better suit your particular use case. In particular, you can:
- Specify custom primary key fields
- Customize the storage and retrieval of individual fields
- Customize the interpretation of field arguments
- Define supertype-subtype relationships for fragment matching
- Define patterns for pagination
- Manage client-side local state
To customize cache behavior, provide an options
object to the InMemoryCache
constructor. This object supports the following fields:
Name / Type | Description |
---|---|
| If The default value is |
| If The default value is |
| Include this object to define polymorphic relationships between your schema's types. Doing so enables you to look up cached data by interface or by union. Each key in the object is the For an example, see Defining |
| Include this object to customize the cache's behavior on a type-by-type basis. Each key in the object is the |
| Deprecated. A function that takes a response object and returns a unique identifier to be used when normalizing the data in the store. Deprecated in favor of the |
Customizing cache IDs
You can customize how the InMemoryCache
generates cache IDs for individual types in your schema (see the default behavior). This is helpful especially if a type uses a field (or fields!) besides id
or _id
as its unique identifier.
To accomplish this, you define a TypePolicy
for each type you want to customize. You specify all of your cache's typePolicies
in the options
object you provide to the InMemoryCache
constructor.
Include a keyFields
field in relevant TypePolicy
objects, like so:
const cache = new InMemoryCache({
typePolicies: {
Product: {
// In an inventory management system, products might be identified
// by their UPC.
keyFields: ["upc"],
},
Person: {
// In a user account system, the combination of a person's name AND email
// address might uniquely identify them.
keyFields: ["name", "email"],
},
Book: {
// If one of the keyFields is an object with fields of its own, you can
// include those nested keyFields by using a nested array of strings:
keyFields: ["title", "author", ["name"]],
},
AllProducts: {
// Singleton types that have no identifying field can use an empty
// array for their keyFields.
keyFields: [],
},
},
});
This example shows a variety of typePolicies
with different keyFields
:
- The
Product
type uses itsupc
field as its identifying field. - The
Person
type uses the combination of both itsname
andemail
fields. - The
Book
type includes a subfield as part of its cache ID.- The
["name"]
item indicates that thename
field of the previous field in the array (author
) is part of the cache ID. TheBook
'sauthor
field must be an object that includes aname
field for this to be valid. - A valid cache ID for the
Book
type has the following structure:Book:{"title":"Fahrenheit 451","author":{"name":"Ray Bradbury"}}
- The
- The
AllProducts
type illustrates a special strategy for a singleton type. If the cache will only ever contain oneAllProducts
object and that object has no identifying fields, you can provide an empty array for itskeyFields
.
If an object has multiple keyFields
, the cache ID always lists those fields in the same order to ensure uniqueness.
Note that these keyFields
strings always refer to the actual field names as defined in your schema, meaning the ID computation is not sensitive to field aliases.
Calculating an object's cache ID
If you define a custom cache ID that uses multiple fields, it can be challenging to calculate and provide that ID to methods that require it (such as cache.readFragment
).
To help with this, you can use the cache.identify
method to calculate the cache ID for any normalized object you fetch from your cache. See Obtaining an object's custom ID.
Customizing identifier generation globally
If you need to define a single fallback keyFields
function that isn't specific to any particular __typename
, you can use the dataIdFromObject
function that was introduced in Apollo Client 2.x:
import { defaultDataIdFromObject } from '@apollo/client';
const cache = new InMemoryCache({
dataIdFromObject(responseObject) {
switch (responseObject.__typename) {
case 'Product': return `Product:${responseObject.upc}`;
case 'Person': return `Person:${responseObject.name}:${responseObject.email}`;
default: return defaultDataIdFromObject(responseObject);
}
}
});
The
dataIdFromObject
API is included in Apollo Client 3 to ease the transition from Apollo Client 2.x. The API might be removed in a future version of@apollo/client
.
Notice that the above function still uses different logic to generate keys based on an object's __typename
. In a case like this, you should almost always define keyFields
arrays for the Product
and Person
types via typePolicies
.
This code also has the following drawbacks:
- It's sensitive to aliasing mistakes.
- It does nothing to protect against undefined
object
properties. - Accidentally using different key fields at different times can cause inconsistencies in the cache.
Disabling normalization
You can instruct the InMemoryCache
not to normalize objects of a particular type. This can be useful for metrics and other transient data that's identified by a timestamp and never receives updates.
To disable normalization for a type, define a TypePolicy
for the type (as shown in Customizing cache IDs) and set the policy's keyFields
field to false
.
Objects that are not normalized are instead embedded within their parent object in the cache. You can't access these objects directly, but you can access them via their parent.
TypePolicy
fields
To customize how the cache interacts with specific types in your schema, you can provide an object mapping __typename
strings to TypePolicy
objects when you create a new InMemoryCache
object.
A TypePolicy
object can include the following fields:
type TypePolicy = {
// Allows defining the primary key fields for this type, either using an
// array of field names, a function that returns an arbitrary string, or
// false to disable normalization for objects of this type.
keyFields?: KeySpecifier | KeyFieldsFunction | false;
// If your schema uses a custom __typename for any of the root Query,
// Mutation, and/or Subscription types (rare), set the corresponding
// field below to true to indicate that this type serves as that type.
queryType?: true,
mutationType?: true,
subscriptionType?: true,
fields?: {
[fieldName: string]:
| FieldPolicy<StoreValue>
| FieldReadFunction<StoreValue>;
}
};
// Recursive type aliases are coming in TypeScript 3.7, so this isn't the
// actual type we use, but it's what it should be:
type KeySpecifier = (string | KeySpecifier)[];
type KeyFieldsFunction = (
object: Readonly<StoreObject>,
context: {
typename: string;
selectionSet?: SelectionSetNode;
fragmentMap?: FragmentMap;
},
) => string | null | void;
Overriding root operation types (uncommon)
In addition to keyFields
, a TypePolicy
can indicate that it represents the root query, mutation, or subscription type by setting queryType
, mutationType
, or subscriptionType
as true
:
const cache = new InMemoryCache({
typePolicies: {
UnconventionalRootQuery: {
// The RootQueryFragment can only match if the cache knows the __typename
// of the root query object.
queryType: true,
},
},
});
const result = cache.readQuery({
query: gql`
query MyQuery {
...RootQueryFragment
}
fragment RootQueryFragment on UnconventionalRootQuery {
field1
field2 {
subfield
}
}
`,
});
const equivalentResult = cache.readQuery({
query: gql`
query MyQuery {
field1
field2 {
subfield
}
}
`,
});
The cache normally obtains __typename
information by adding the __typename
field to every query selection set it sends to the server. It could technically use the same trick for the outermost selection set of every operation, but the __typename
of the root query or mutation is almost always "Query"
or "Mutation"
, so the cache assumes those common defaults unless instructed otherwise in a TypePolicy
.
Compared to the __typename
s of entity objects like Book
or Person
, which are vital to proper identification and normalization, the __typename
of the root query or mutation type is not nearly as useful or important, because those types are singletons with only one instance per client.
The fields
property
The final property within TypePolicy
is the fields
property, which is a map from string field names to FieldPolicy
objects. For more information on this field, see Customizing the behavior of cached fields.