Docs
Launch GraphOS Studio
You're viewing documentation for a previous version of this software. Switch to the latest stable version.

Interacting with cached data


The ApolloClient object provides the following methods for interacting with cached data:

  • readQuery
  • readFragment
  • writeQuery
  • writeFragment

These methods are described in detail below.

Important: You should call these methods on your app's ApolloClient object, not directly on the cache. By doing so, the ApolloClient object broadcasts cache changes to your entire app, which enables automatic UI updates. If you call these methods directly on the cache instead, changes are not broadcast.

All code samples below assume that you have initialized an instance of ApolloClient and that you have imported the gql tag from graphql-tag.

readQuery

The readQuery method enables you to run queries directly on your cache.

If your cache contains all of the data necessary to fulfill a specified , readQuery returns a data object in the shape of your , just like a server does.

If your cache doesn't contain all of the data necessary to fulfill a specified , readQuery throws an error. It never attempts to fetch data from a remote server.

Pass readQuery a string like so:

const { todo } = client.readQuery({
query: gql`
query ReadTodo {
todo(id: 5) {
id
text
completed
}
}
`,
});

You can provide to readQuery like so:

const { todo } = client.readQuery({
query: gql`
query ReadTodo($id: Int!) {
todo(id: $id) {
id
text
completed
}
}
`,
variables: {
id: 5,
},
});

Do not modify the return value of readQuery. The same object might be returned to multiple components. To update data in the cache, instead create a replacement object and pass it to

.

readFragment

The readFragment method enables you to read data from any normalized cache object that was stored as part of any result. Unlike readQuery, calls to readFragment do not need to conform to the structure of one of your data 's supported queries.

Here's an example:

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 , id, is the

that was assigned to the object you want to read from the cache. This should match the value that your dataIdFromObject function assigned to the object when it was stored.

For example, let's say you initialize ApolloClient like so:

const client = new ApolloClient({
...,
cache: new InMemoryCache({
...,
dataIdFromObject: object => object.id,
}),
});

If a previously executed cached a Todo object with an id of 5, you can read that object from your cache with the following readFragment call:

const todo = client.readFragment({
id: '5',
fragment: gql`
fragment myTodo on Todo {
id
text
completed
}
`,
});

In the example above, if a Todo object with an id of 5 is not in the cache, readFragment returns null. If the Todo object is in the cache but it's missing either a text or completed , readFragment throws an error.

writeQuery and writeFragment

In addition to reading arbitrary data from the cache, you can write arbitrary data to the cache with the writeQuery and writeFragment methods.

Any changes you make to cached data with writeQuery and writeFragment are not pushed to your GraphQL server. If you reload your environment, these changes will disappear.

These methods have the same signature as their read counterparts, except they require an additional data .

For example, the following call to writeFragment locally updates the completed flag for a Todo object with an id of 5:

client.writeFragment({
id: '5',
fragment: gql`
fragment myTodo on Todo {
completed
}
`,
data: {
completed: true,
},
});

All subscribers to the cache see this change and update your application's UI accordingly.

As another example, you can combine readQuery and writeQuery to add a new Todo item to your cached to-do list:

const query = gql`
query MyTodoAppQuery {
todos {
id
text
completed
}
}
`;
// Get the current to-do list
const data = client.readQuery({ query });
const myNewTodo = {
id: '6',
text: 'Start using Apollo Client.',
completed: false,
__typename: 'Todo',
};
// Write back to the to-do list and include the new item
client.writeQuery({
query,
data: {
todos: [...data.todos, myNewTodo],
},
});

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 cache

Sometimes it makes sense to not use the cache for a specific . This can be done using the no-cache fetchPolicy. The no-cache policy does not write to the cache with the response. This may be useful for sensitive data like passwords that you don’t want to keep in the cache.

Updating after a mutation

In some cases, just using dataIdFromObject is not enough for your application UI to update correctly. For example, if you want to add something to a list of objects without refetching the entire list, or if there are some objects that to which you can't assign an object identifier, cannot update existing queries for you. Read on to learn about the other tools at your disposal.

refetchQueries is the simplest way of updating the cache. With refetchQueries you can specify one or more queries that you want to run after a is completed in order to refetch the parts of the store that may have been affected by the mutation:

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' },
}],
})

Please note that if you call refetchQueries with an array of strings, then will look for any previously called queries that have the same names as the provided strings. It will then refetch those queries with their current .

A very common way of using refetchQueries is to import queries defined for other components to make sure that those components will be updated:

import RepoCommentsQuery from '../queries/RepoCommentsQuery';
mutate({
//... insert comment mutation
refetchQueries: [{
query: RepoCommentsQuery,
variables: { repoFullName: 'apollographql/apollo-client' },
}],
})

Using update gives you full control over the cache, allowing you to make changes to your data model in response to a in any way you like. update is the recommended way of updating the cache after a . It is explained in full

.

import CommentAppQuery from '../queries/CommentAppQuery';
const SUBMIT_COMMENT_MUTATION = gql`
mutation SubmitComment($repoFullName: String!, $commentContent: String!) {
submitComment(
repoFullName: $repoFullName
commentContent: $commentContent
) {
postedBy {
login
html_url
}
createdAt
content
}
}
`;
const CommentsPageWithMutations = () => (
<Mutation mutation={SUBMIT_COMMENT_MUTATION}>
{mutate => {
<AddComment
submit={({ repoFullName, commentContent }) =>
mutate({
variables: { repoFullName, commentContent },
update: (store, { data: { submitComment } }) => {
// Read the data from our cache for this query.
const data = store.readQuery({ query: CommentAppQuery });
// Add our comment from the mutation to the end.
data.comments.push(submitComment);
// Write our data back to the cache.
store.writeQuery({ query: CommentAppQuery, data });
}
})
}
/>;
}}
</Mutation>
);

Incremental loading: fetchMore

fetchMore can be used to update the result of a based on the data returned by another query. Most often, it is used to handle infinite-scroll pagination or other situations where you are loading more data when you already have some.

In our GitHunt example, we have a paginated feed that displays a list of GitHub repositories. When we hit the "Load More" button, we don't want to throw away the repository information it has already loaded. Instead, it should just append the newly loaded repositories to the list that Apollo Client already has in the store. With this update, our UI component should re-render and show us all of the available repositories.

Let's see how to do that with the fetchMore method on a :

const FEED_QUERY = gql`
query Feed($type: FeedType!, $offset: Int, $limit: Int) {
currentUser {
login
}
feed(type: $type, offset: $offset, limit: $limit) {
id
# ...
}
}
`;
const FeedWithData = ({ match }) => (
<Query
query={FEED_QUERY}
variables={{
type: match.params.type.toUpperCase() || "TOP",
offset: 0,
limit: 10
}}
fetchPolicy="cache-and-network"
>
{({ data, fetchMore }) => (
<Feed
entries={data.feed || []}
onLoadMore={() =>
fetchMore({
variables: {
offset: data.feed.length
},
updateQuery: (prev, { fetchMoreResult }) => {
if (!fetchMoreResult) return prev;
return Object.assign({}, prev, {
feed: [...prev.feed, ...fetchMoreResult.feed]
});
}
})
}
/>
)}
</Query>
);

The fetchMore method takes a map of variables to be sent with the new . Here, we're setting the offset to feed.length so that we fetch items that aren't already displayed on the feed. This map is merged with the one that's been specified for the associated with the component. This means that other variables, e.g. the limit , will have the same value as they do within the component .

It can also take a query named , which can be a containing a that will be fetched in order to fetch more information; we refer to this as the fetchMore . By default, the fetchMore is the query associated with the container, in this case the FEED_QUERY.

When we call fetchMore, will fire the fetchMore and use the logic in the updateQuery option to incorporate that into the original result. The named updateQuery should be a function that takes the previous result of the associated with your component (i.e. FEED_QUERY in this case) and the information returned by the fetchMore and return a combination of the two.

Here, the fetchMore is the same as the query associated with the component. Our updateQuery takes the new feed items returned and just appends them onto the feed items that we'd asked for previously. With this, the UI will update and the feed will contain the next page of items!

Although fetchMore is often used for pagination, there are many other cases in which it is applicable. For example, suppose you have a list of items (say, a collaborative todo list) and you have a way to fetch items that have been updated after a certain time. Then, you don't have to refetch the whole todo list to get updates: you can just incorporate the newly added items with fetchMore, as long as your updateQuery function correctly merges the new results.

The @connection directive

Fundamentally, paginated queries are the same as any other with the exception that calls to fetchMore update the same cache key. Since these queries are cached by both the initial and their parameters, a problem arises when later retrieving or updating paginated queries in the cache. We don’t care about pagination such as limits, offsets, or cursors outside of the need to fetchMore, nor do we want to provide them simply for accessing cached data.

To solve this 1.6 introduced the @connection to specify a custom store key for results. A connection allows us to set the cache key for a and to filter which actually alter the .

To use the @connection , simply add the directive to the segment of the you want a custom store key for and provide the key parameter to specify the store key. In addition to the key parameter, you can also include the optional filter parameter, which takes an array of names to include in the generated custom store key.

const query = gql`
query Feed($type: FeedType!, $offset: Int, $limit: Int) {
feed(type: $type, offset: $offset, limit: $limit) @connection(key: "feed", filter: ["type"]) {
...FeedEntry
}
}
`

With the above , even with multiple fetchMores, the results of each feed update will always result in the feed key in the store being updated with the latest accumulated values. In this example, we also use the @connection 's optional filter to include the type in the store key, which results in multiple store values that accumulate queries from each type of feed.

Now that we have a stable store key, we can easily use writeQuery to perform a store update, in this case clearing out the feed.

client.writeQuery({
query: gql`
query Feed($type: FeedType!) {
feed(type: $type) @connection(key: "feed", filter: ["type"]) {
id
}
}
`,
variables: {
type: "top",
},
data: {
feed: [],
},
});

Note that because we are only using the type in the store key, we don't have to provide offset or limit.

Cache redirects with cacheRedirects

In some cases, a requests data that already exists in the client store under a different key. A very common example of this is when your UI has a list view and a detail view that both use the same data. The list view might run the following query:

query ListView {
books {
id
title
abstract
}
}

When a specific book is selected, the detail view displays an individual item using this :

query DetailView {
book(id: $id) {
id
title
abstract
}
}

Note: The data returned by the list has to include all the data the specific query needs. If the specific book query fetches a that the list query doesn't return cannot return the data from the cache.

We know that the data is most likely already in the client cache, but because it's requested with a different , doesn't know that. In order to tell Apollo Client where to look for the data, we can define custom :

import { InMemoryCache } from 'apollo-cache-inmemory';
const cache = new InMemoryCache({
cacheRedirects: {
Query: {
book: (_, args, { getCacheKey }) =>
getCacheKey({ __typename: 'Book', id: args.id })
},
},
});

Note: This'll also work with custom dataIdFromObject methods as long as you use the same one.

will use the ID returned by the custom to look up the item in its cache. getCacheKey is passed inside the third to the to generate the key of the object based on its __typename and id.

To figure out what you should put in the __typename property run one of the queries in and get the __typename :

query ListView {
books {
__typename
}
}
# or
query DetailView {
book(id: $id) {
__typename
}
}

The value that's returned (the name of your type) is what you need to put into the __typename property.

It is also possible to return a list of IDs:

cacheRedirects: {
Query: {
books: (_, args, { getCacheKey }) =>
args.ids.map(id =>
getCacheKey({ __typename: 'Book', id: id }))
}
}

Resetting the store

Sometimes, you may want to reset the store entirely, such as

. To accomplish this, use client.resetStore to clear out your Apollo cache. Since client.resetStore also refetches any of your active queries for you, it is asynchronous.

export default withApollo(graphql(PROFILE_QUERY, {
props: ({ data: { loading, currentUser }, ownProps: { client }}) => ({
loading,
currentUser,
resetOnLogout: async () => client.resetStore(),
}),
})(Profile));

To register a callback function to be executed after the store has been reset, call client.onResetStore and pass in your callback. If you would like to register multiple callbacks, simply call client.onResetStore again. All of your callbacks will be pushed into an array and executed concurrently.

In this example, we're using client.onResetStore to write our default values to the cache for

. This is necessary if you're using apollo-link-state for local state management and calling client.resetStore anywhere in your application.

import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { withClientState } from 'apollo-link-state';
import { resolvers, defaults } from './resolvers';
const cache = new InMemoryCache();
const stateLink = withClientState({ cache, resolvers, defaults });
const client = new ApolloClient({
cache,
link: stateLink,
});
client.onResetStore(stateLink.writeDefaults);

You can also call client.onResetStore from your React components. This can be useful if you would like to force your UI to rerender after the store has been reset.

If you would like to unsubscribe your callbacks from resetStore, use the return value of client.onResetStore for your unsubscribe function.

import { withApollo } from "react-apollo";
export class Foo extends Component {
constructor(props) {
super(props);
this.unsubscribe = props.client.onResetStore(
() => this.setState({ reset: false })
);
this.state = { reset: false };
}
componentDidUnmount() {
this.unsubscribe();
}
render() {
return this.state.reset ? <div /> : <span />
}
}
export default withApollo(Foo);

If you want to clear the store but don't want to refetch active queries, use client.clearStore() instead of client.resetStore().

Server side rendering

First, you will need to initialize an InMemoryCache on the server and create an instance of ApolloClient. In the initial serialized HTML payload from the server, you should include a script tag that extracts the data from the cache. (The .replace() is necessary to prevent script injection attacks)

`<script>
window.__APOLLO_STATE__=${JSON.stringify(cache.extract()).replace(/</g, '\\u003c')}
</script>`

On the client, you can rehydrate the cache using the initial data passed from the server:

cache: new Cache().restore(window.__APOLLO_STATE__)

If you would like to learn more about server side rendering, please check out our more in depth guide

.

Cache persistence

If you would like to persist and rehydrate your Apollo Cache from a storage provider like AsyncStorage or localStorage, you can use

. apollo-cache-persist works with all Apollo caches, including InMemoryCache & Hermes, and a variety of different
storage providers
.

To get started, simply pass your Apollo Cache and a storage provider to persistCache. By default, the contents of your Apollo Cache will be immediately restored asynchronously, and persisted upon every write to the cache with a short configurable debounce interval.

Note: The persistCache method is async and returns a Promise.

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,
}).then(() => {
// 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

.

Previous
Configuring the cache
Next
Using Apollo with TypeScript
Edit on GitHubEditForumsDiscord

© 2024 Apollo Graph Inc.

Privacy Policy

Company