Caching data
InMemoryCache
apollo-cache-inmemory is the default cache implementation for Apollo Client 2.0.
InMemoryCache is a normalized data store that supports all of Apollo Client 1.0’s features without the dependency on Redux.
In some instances, you may need to manipulate the cache directly, such as updating the store after a mutation. We’ll cover some common use cases here.
Installation
npm install apollo-cache-inmemory --save
After installing the package, you’ll want to initialize the cache constructor. Then, you can pass in your newly created cache to ApolloClient.
import { InMemoryCache } from 'apollo-cache-inmemory'; import { HttpLink } from 'apollo-link-http'; import ApolloClient from 'apollo-client'; const cache = new InMemoryCache(); const client = new ApolloClient({ link: new HttpLink(), cache });
Configuration
The
InMemoryCache constructor takes an optional config object with properties to customize your cache:
- addTypename: A boolean to determine whether to add __typename to the document (default:
true)
- dataIdFromObject: A function that takes a data object and returns a unique identifier to be used when normalizing the data in the store. Learn more about how to customize
dataIdFromObjectin the Normalization section.
- fragmentMatcher: By default, the
InMemoryCacheuses a heuristic fragment matcher. If you are using fragments on unions and interfaces, you will need to use an
IntrospectionFragmentMatcher. For more information, please read our guide to setting up fragment matching for unions & interfaces.
- cacheResolvers: A map of custom ways to resolve data from other parts of the cache.
Normalization
The
InMemoryCache normalizes your data before saving it to the store by splitting the result into individual objects, creating a unique identifier for each object, and storing those objects in a flattened data structure. By default,
InMemoryCache will attempt to use the commonly found primary keys of
id and
_id for the unique identifier if they exist along with
__typename on an object.
If
id and
_id are not specified, or if
__typename is not specified,
InMemoryCache will fall back to the path to the object in the query, such as
ROOT_QUERY.allPeople.0 for the first record returned on the
allPeople root query.
That would make data for given type scoped for
allPeople query and other queries would have to fetch their own separate objects.
This “getter” behavior for unique identifiers can be configured manually via the
dataIdFromObject option passed to the
InMemoryCache constructor, so you can pick which field is used if some of your data follows unorthodox primary key conventions so it could be referenced by any query.
For example, if you wanted to key off of the
key field for all of your data, you could configure
dataIdFromObject like so:
const cache = new InMemoryCache({ dataIdFromObject: object => object.key || null });
Note that Apollo Client doesn’t add the type name to the cache key when you specify a custom
dataIdFromObject, so if your IDs are not unique across all objects, you might want to include the
__typename in your
dataIdFromObject.
You can use different unique identifiers for different data types by keying off of the
__typename property attached to every object typed by GraphQL. For example:..
Any code demonstration in the following sections will assume that we have already initialized an instance of
ApolloClient and that we have imported the
gql tag from
graphql-tag.
readQuery
The
readQuery method is very similar to the
query method on
ApolloClient except that
readQuery will never make a request to your GraphQL server. The
query method, on the other hand, may send a request to your server if the appropriate data is not in your cache whereas
readQuery will throw an error if the data is not in your cache.
readQuery will always read from the cache. You can use
readQuery by giving it a GraphQL query like so:
const { todo } = client.readQuery({ query: gql` query ReadTodo { todo(id: 5) { id text completed } } `, });
If all of the data needed to fulfill this read is in Apollo Client’s normalized data cache then a data object will be returned in the shape of the query you wanted to read. If not all of the data needed to fulfill this read is in Apollo Client’s cache then an error will be thrown instead, so make sure to only read data that you know you have!
You can also pass variables into
readQuery.
const { todo } = client.readQuery({ query: gql` query ReadTodo($id: Int!) { todo(id: $id) { id text completed } } `, variables: { id: 5, }, });
readFragment
This method allows you great flexibility around the data in your cache. Whereas
readQuery only allowed you to read data from your root query type,
readFragment allows you to read data from any node you have queried. This is incredibly powerful. You use this method as follows:
const todo = client.readFragment({ id: ..., // `id` is any id that could be returned by `dataIdFromObject`. fragment: gql` fragment myTodo on Todo { id text completed } `, });
The first argument is the id of the data you want to read from the cache. That id must be a value that was returned by the
dataIdFromObject function you defined when initializing
ApolloClient. So for example if you initialized
ApolloClient like so:
const client = new ApolloClient({ ..., cache: new InMemoryCache({ ..., dataIdFromObject: object => object.id, }), });
…and you requested a todo before with an id of
5, then you can read that todo out of your cache with the following:
const todo = client.readFragment({ id: '5', fragment: gql` fragment myTodo on Todo { id text completed } `, });
Note: Most people add a
__typenameto the id in
dataIdFromObject. If you do this then don’t forget to add the
__typenamewhen you are reading a fragment as well. So for example your id may be
Todo_5and not just
5.
If a todo with that id does not exist in the cache you will get
null back. If a todo of that id does exist in the cache, but that todo does not have the
text field then an error will be thrown.
The beauty of
readFragment is that the todo could have come from anywhere! The todo could have been selected as a singleton (
{ todo(id: 5) { ... } }), the todo could have come from a list of todos (
{ todos { ... } }), or the todo could have come from a mutation (
mutation { createTodo { ... } }). As long as at some point your GraphQL server gave you a todo with the provided id and fields
id,
text, and
completed you can read it from the cache at any part of your code.
writeQuery and writeFragment
Not only can you read arbitrary data from the Apollo Client cache, but you can also write any data that you would like to the cache. The methods you use to do this are
writeQuery and
writeFragment. They will allow you to change data in your local cache, but it is important to remember that they will not change any data on your server. If you reload your environment then changes made with
writeQuery and
writeFragment will disappear.
These methods have the same signature as their
readQuery and
readFragment counterparts except they also require an additional
data variable. So for example, if you wanted to update the
completed flag locally for your todo with id
'5' you could execute the following:
client.writeFragment({ id: '5', fragment: gql` fragment myTodo on Todo { completed } `, data: { completed: true, }, });
Any subscriber to the Apollo Client store will instantly see this update and render new UI accordingly.
Note: Again, remember that using
writeQueryor
writeFragmentonly changes data locally. If you reload your environment then changes made with these methods will no longer exist.
Or if you wanted to add a new todo to a list fetched from the server, you could use
readQuery and
writeQuery together.], }, });
Recipes
Here are some common situations where you would need to access the cache directly. If you’re manipulating the cache in an interesting way and would like your example to be featured, please send in a pull request!
Bypassing the cacheSometimes it makes sense to not use the cache for a specfic operation. This can be done using either the
network-onlyor
no-cachefetchPolicy. The key difference between these two policies is that
network-onlystill saves the response to the cache for later use, bypassing the reading and forcing a network request. The
no-cachepolicy does not read, nor does:
mutate({ //... insert comment mutation refetchQueries: [{ query: gql` query updateCache($repoName: String!) { entry(repoFullName: $repoName) { id comments { postedBy { login html_url } createdAt content } } } `, variables: { repoName: 'apollographql/apollo-client' }, }], }):
const FEED_QUERY = gql` query Feed($type: FeedType!, $offset: Int, $limit: Int) { currentUser { login } feed(type: $type, offset: $offset, limit: $limit) { id # ... } } `; const FeedWith> ); have a stable cache location for query results, Apollo Client 1.6 introduced the
@connection directive, which can be used to specify a custom store key for results.ResolversResolvers: {Resolvers: { Query: { books: (_, args) => args.ids.map(id => toIdValue(cache.config.dataIdFromObject({ _ our.
import { AsyncStorage } from 'react-native'; import { InMemoryCache } from 'apollo-cache-inmemory'; import { persistCache } from 'apollo-cache-persist'; const cache = new InMemoryCache(); persistCache({ cache, storage: AsyncStorage, }); // Continue setting up Apollo as usual.
For more advanced usage, such as persisting the cache when the app is in the background, and additional configuration options, please check the README of
apollo-cache-persist.