UNPKG

13.9 kBMarkdownView Raw
1---
2title: InMemoryCache
3description: An explanation of `apollo-cache-inmemory`
4---
5
6`apollo-cache-inmemory` is the recommended 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.
7
8In 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](#recipes).
9
10<h2 id="installation">Installation</h2>
11
12```
13npm install apollo-cache-inmemory --save
14```
15
16After installing the package, you'll want to initialize the cache constructor. Then, you can pass in your newly created cache to ApolloClient.
17
18```js
19import { InMemoryCache } from 'apollo-cache-inmemory';
20import { HttpLink } from 'apollo-link-http';
21import ApolloClient from 'apollo-client';
22
23const cache = new InMemoryCache();
24
25const client = new ApolloClient({
26 link: new HttpLink(),
27 cache
28});
29```
30
31<h2 id="configuration">Configuration</h2>
32
33The `InMemoryCache` constructor takes an optional config object with properties to customize your cache:
34
35- addTypename: A boolean to determine whether to add __typename to the document (default: `true`)
36- 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 `dataIdFromObject` in the [Normalization](#normalization) section.
37- fragmentMatcher: By default, the `InMemoryCache` uses 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](https://www.apollographql.com/docs/react/advanced/fragments.html#fragment-matcher).
38
39<h2 id="normalization">Normalization</h2>
40
41The `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.
42
43If `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.
44
45This "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.
46
47For example, if you wanted to key off of the `key` field for all of your data, you could configure `dataIdFromObject` like so:
48
49```js
50const cache = new InMemoryCache({
51 dataIdFromObject: object => object.key
52});
53```
54
55This also allows you to use different unique identifiers for different data types by keying off of the `__typename` property attached to every object typed by GraphQL. For example:
56
57```js
58const cache = new InMemoryCache({
59 dataIdFromObject: object => {
60 switch (object.__typename) {
61 case 'foo': return object.key; // use `key` as the primary key
62 case 'bar': return object.blah; // use `blah` as the priamry key
63 default: return object.id || object._id; // fall back to `id` and `_id` for all other types
64 }
65 }
66});
67```
68
69<h2 id="direct">Direct Cache Access</h2>
70
71To interact directly with your cache, you can use the Apollo Client class methods readQuery, readFragment, writeQuery, and writeFragment. These methods are available to us via the [`DataProxy` interface](https://github.com/apollographql/apollo-client/blob/master/packages/apollo-cache/src/types/DataProxy.ts). Accessing these methods will vary slightly based on your view layer implementation. If you are using React, you can wrap your component in the `withApollo` higher order component, which will give you access to `this.props.client`. From there, you can use the methods to control your data.
72
73Any 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`.
74
75<h3 id="readquery">readQuery</h3>
76
77The `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:
78
79```js
80const { todo } = client.readQuery({
81 query: gql`
82 query ReadTodo {
83 todo(id: 5) {
84 id
85 text
86 completed
87 }
88 }
89 `,
90});
91```
92
93If 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!
94
95You can also pass variables into `readQuery`.
96
97```js
98const { todo } = client.readQuery({
99 query: gql`
100 query ReadTodo($id: Int!) {
101 todo(id: $id) {
102 id
103 text
104 completed
105 }
106 }
107 `,
108 variables: {
109 id: 5,
110 },
111});
112```
113
114<h3 id="readfragment">readFragment</h3>
115
116This 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:
117
118```js
119const todo = client.readFragment({
120 id: ..., // `id` is any id that could be returned by `dataIdFromObject`.
121 fragment: gql`
122 fragment myTodo on Todo {
123 id
124 text
125 completed
126 }
127 `,
128});
129```
130
131The 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:
132
133```js
134const client = new ApolloClient({
135 ...,
136 dataIdFromObject: object => object.id,
137});
138```
139
140…and you requested a todo before with an id of `5`, then you can read that todo out of your cache with the following:
141
142```js
143const todo = client.readFragment({
144 id: '5',
145 fragment: gql`
146 fragment myTodo on Todo {
147 id
148 text
149 completed
150 }
151 `,
152});
153```
154
155> **Note:** Most people add a `__typename` to the id in `dataIdFromObject`. If you do this then don’t forget to add the `__typename` when you are reading a fragment as well. So for example your id may be `Todo_5` and not just `5`.
156
157If 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.
158
159The 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.
160
161<h3 id="writequery-and-writefragment">writeQuery` and `writeFragment</h3>
162
163Not 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.
164
165These 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:
166
167```js
168client.writeFragment({
169 id: '5',
170 fragment: gql`
171 fragment myTodo on Todo {
172 completed
173 }
174 `,
175 data: {
176 completed: true,
177 },
178});
179```
180
181Any subscriber to the Apollo Client store will instantly see this update and render new UI accordingly.
182
183> **Note:** Again, remember that using `writeQuery` or `writeFragment` only changes data *locally*. If you reload your environment then changes made with these methods will no longer exist.
184
185Or if you wanted to add a new todo to a list fetched from the server, you could use `readQuery` and `writeQuery` together.
186
187```js
188const query = gql`
189 query MyTodoAppQuery {
190 todos {
191 id
192 text
193 completed
194 }
195 }
196`;
197
198const data = client.readQuery({ query });
199
200const myNewTodo = {
201 id: '6',
202 text: 'Start using Apollo Client.',
203 completed: false,
204};
205
206client.writeQuery({
207 query,
208 data: {
209 todos: [...data.todos, myNewTodo],
210 },
211});
212```
213
214<h2 id="recipes">Recipes</h2>
215
216Here 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!
217
218<h3 id="server">Server side rendering</h3>
219
220First, 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.
221
222```js
223`<script>
224 window.__APOLLO_STATE__=${JSON.stringify(cache.extract())}
225</script>`
226```
227
228On the client, you can rehydrate the cache using the initial data passed from the server:
229
230```js
231cache: new Cache().restore(window.__APOLLO_STATE__)
232```
233
234If you would like to learn more about server side rendering, please check our our more in depth guide [here].
235<!---
236TODO (PEGGY): Add link to SSR
237-->
238
239<h3 id="server">Updating the cache after a mutation</h3>
240
241Being able to read and write to the Apollo cache from anywhere in your application gives you a lot of power over your data. However, there is one place where we most often want to update our cached data: after a mutation. As such, Apollo Client has optimized the experience for updating your cache with the read and write methods after a mutation with the `update` function. Let us say that we have the following GraphQL mutation:
242
243```graphql
244mutation TodoCreateMutation($text: String!) {
245 createTodo(text: $text) {
246 id
247 text
248 completed
249 }
250}
251```
252
253We may also have the following GraphQL query:
254
255```graphql
256query TodoAppQuery {
257 todos {
258 id
259 text
260 completed
261 }
262}
263```
264
265At the end of our mutation we want our query to include the new todo like we had sent our `TodoAppQuery` a second time after the mutation finished without actually sending the query. To do this we can use the `update` function provided as an option of the `client.mutate` method. To update your cache with the mutation just write code that looks like:
266
267```js
268// We assume that the GraphQL operations `TodoCreateMutation` and
269// `TodoAppQuery` have already been defined using the `gql` tag.
270
271const text = 'Hello, world!';
272
273client.mutate({
274 mutation: TodoCreateMutation,
275 variables: {
276 text,
277 },
278 update: (proxy, { data: { createTodo } }) => {
279 // Read the data from our cache for this query.
280 const data = proxy.readQuery({ query: TodoAppQuery });
281
282 // Add our todo from the mutation to the end.
283 data.todos.push(createTodo);
284
285 // Write our data back to the cache.
286 proxy.writeQuery({ query: TodoAppQuery, data });
287 },
288});
289```
290
291The first `proxy` argument is an instance of [`DataProxy`][] has the same four methods that we just learned exist on the Apollo Client: `readQuery`, `readFragment`, `writeQuery`, and `writeFragment`. The reason we call them on a `proxy` object here instead of on our `client` instance is that we can easily apply optimistic updates (which we will demonstrate in a bit). The `proxy` object also provides an isolated transaction which shields you from any other mutations going on at the same time, and the `proxy` object also batches writes together until the very end.
292
293If you provide an `optimisticResponse` option to the mutation then the `update` function will be run twice. Once immediately after you call `client.mutate` with the data from `optimisticResponse`. After the mutation successfully executes against the server the changes made in the first call to `update` will be rolled back and `update` will be called with the *actual* data returned by the mutation and not just the optimistic response.
294
295Putting it all together:
296
297```js
298const text = 'Hello, world!';
299
300client.mutate({
301 mutation: TodoCreateMutation,
302 variables: {
303 text,
304 },
305 optimisticResponse: {
306 id: -1, // -1 is a temporary id for the optimistic response.
307 text,
308 completed: false,
309 },
310 update: (proxy, { data: { createTodo } }) => {
311 const data = proxy.readQuery({ query: TodoAppQuery });
312 data.todos.push(createTodo);
313 proxy.writeQuery({ query: TodoAppQuery, data });
314 },
315});
316```
317
318As you can see the `update` function on `client.mutate` provides extra change management functionality specific to the use case of a mutation while still providing you the powerful data control APIs that are available on `client`.
319
320The `update` function is not a good place for side-effects as it may be called multiple times. Also, you may not call any of the methods on `proxy` asynchronously.
321