Apollo Client Cache
Apollo caches the result of every graphql query in a normalized cache.
- The cache normalizes query response objects before it saves them to its internal data store.
- though like the result of a graphql query, the object can be arbitrarily deep. However, references are used, so that the same object is never stored twice.
Normalization
Normalization happens in the following steps:
- The cache generates a CacheId for every identifiable object included in the response.
- The CacheId will be in format:
<__typename>:<id>
(ex.Bucket:232
)
- The CacheId will be in format:
- All the objects are stored by that generated ID in a flat lookup table.
- Whenever an incoming object is stored with the same CacheId as an existing object, the fields of those objects are merged.
- this means that the only time anything is overwritten is when the field names are the same. If the incoming object has different fieldnames than the existing one, they will be preserved
- ex. this has the further implication that if we had one query that returned a list of books (but only the title and author), then we subsequently clicked on a book and another query was made to get all the metadata data (description, page count, reviews etc.), that single book would not have been overwritten. The additional fields from the second query would have simply been merged.
The apollo cache takes the following shape:
{
"__typename": "Person",
"id": "cGVvcGxlOjE=",
"name": "Luke Skywalker",
"homeworld": {
"__ref": "Planet:cGxhbmV0czox"
}
}
Interacting directly with the Cache
We can read/write to the Apollo cache without interacting with our Graphql server.
We can do this in a few different ways:
- using regular Graphql queries to manage both remote and local data
readQuery
/writeQuery
/updateQuery
- using Graphql fragments to access the fields of a cached object without having to compose an entire query to reach that object.
readFragment
/writeFragment
/updateFragment
- modify the cache directly without using Graphql at all
cache.modify
Manually updating the cache
When a single resource is updated with a mutation, Apollo handles caching for us (as long as we return the id
and fields that were updated).
- creating, deleting, or updating many at once will require us to manually update the cache.
useMutation
has an update
function, whose purpose is to modify your cached data to match the modifications that a mutation makes to your back-end data
- this method allows us to interact with the cache as if we were interacting with a graphql API.
- ex. we can make general queries, as well as use fragments to help
- we can interact directly with the cache with
readQuery, writeQuery, readFragment, writeFragment
, which are methods on theApolloClient
class - we can get the cached version of an entity with
cache.identify
- an input
{ id: 1, title: '', mediaItems: [{...}] }
gives usNugget:1
- an input
Apollo cache
object
- Here, queries mirror what our gql queries would look like to target the same data from a Graphql server. Therefore, it must be a complete and valid query.
- fragments on the other hand provide more random access (as opposed to sequential access) to our cached data.
- therefore, we need to provide the cacheId as an argument to the fragment methods.
readQuery
- like a regular graphql query, only it is performed on the cache, rather than the GraphQL API
writeQuery
- uses the same shape as
readQuery
, but requires us to include adata
option with the modifications we wish to make - the shape of our query is not enforced, and we can write fields that aren't present in our Graphql schema.
- docs
readFragment
If the object in the cache is missing fields that exist on the fragment, then we will be returned null
.
writeFragment
Just like readFragment
, but like writeQuery
, it requires a data
object with the fields that we are writing.
updateQuery
/ updateFragment
Allows us to read and write cached data in a single method call.
- ApolloClient 3.5 and later
- docs
cache.modify
modify
is a method we can execute on our cache that lets us modify individual fields directly
- differs from
writeQuery
andwriteFragment
in that it will circumvent anymerge
function we have defined, meaning that fields are always overwritten with exactly the values you specify. - cannot write fields that do not already exist on the object in the cache.
cache.identify
Returns the cacheId for the specified object
Local-only fields
We can store and retrieve fields that only exist in the cache by using the @client
decorator on our graphql query.
query ProductDetails($productId: ID!) {
product(id: $productId) {
name
price
isInCart @client # This is a local-only field
}
}
Type Policies
By defining type policies, we can determine how the cache interacts with specific types in the schema
- done by mapping a
__typename
to the wholeTypePolicy
object. - in other words, the
typePolicies
object haskey
-values
of__typename
-TypePolicy Object
each field in a typePolicies
object is a type's __typename
we can customize how a particular field within our Apollo cache is written to and read. For this, we have 2 methods: merge
and read
.
- with
read
, the cache calls that function whenever your client queries for the field. In the query response, the field is populated with the read function’s return value, instead of the field’s cached value.- Read is useful for manipulating values when they’re read from the cache, for example, things like formatting strings, dates, etc.
- with
merge
, the cache calls that function whenever the field is about to be written with an incoming value. When the write occurs, the field’s new value is set to the merge function’s return value, instead of the original incoming value.- Merge can take incoming data and manipulate it before merging it with existing data. Suppose you want to merge arrays or non-normalized objects.
- to define the policy for a single field, we need to first know which TypePolicy object the field corresponds to.
FieldPolicy
lets us customize how individual fields in the Apollo Client cache are read and written.
Children