Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.
1--- 2title: Cache Updates 3order: 4 4--- 5 6# Cache Updates 7 8As we've learned [on the page on "Normalized 9Caching"](./normalized-caching.md#normalizing-relational-data), when Graphcache receives an API 10result it will traverse and store all its data to its cache in a normalized structure. Each entity 11that is found in a result will be stored under the entity's key. 12 13A query's result is represented as a graph, which can also be understood as a tree structure, 14starting from the root `Query` entity, which then connects to other entities via links, which are 15relations stored as keys, where each entity has records that store scalar values, which are the 16tree's leafs. On the previous page, on ["Local Resolvers"](./local-resolvers.md), we've seen how 17resolvers can be attached to fields to manually resolve other entities (or transform record fields). 18Local Resolvers passively _compute_ results and change how Graphcache traverses and sees its locally 19cached data, however, for **mutations** and **subscriptions** we cannot passively compute data. 20 21When Graphcache receives a mutation or subscription result it still traverses it using the query 22document as we've learned when reading about how Graphcache stores normalized data, 23[quote](./normalized-caching.md/#storing-normalized-data): 24 25> Any mutation or subscription can also be written to this data structure. Once Graphcache finds a 26> keyable entity in their results it's written to its relational table, which may update other 27> queries in our application. 28 29This means that mutations and subscriptions still write and update entities in the cache. These 30updates are then reflected on all active queries that our app uses. However, there are limitations to this. 31While resolvers can be used to passively change data for queries, for mutations 32and subscriptions we sometimes have to write **updaters** to update links and relations. 33This is often necessary when a given mutation or subscription deliver a result that is more granular 34than the cache needs to update all affected entities. 35 36Previously, we've learned about cache updates [on the "Normalized Caching" 37page](./normalized-caching.md#manual-cache-updates). 38 39The `updates` option on `cacheExchange` accepts a map for `Mutation` or `Subscription` keys on which 40we can add "updater functions" to react to mutation or subscription results. These `updates` 41functions look similar to ["Local Resolvers"](./local-resolvers.md) that we've seen in the last 42section and similar to [GraphQL.js' resolvers on the 43server-side](https://www.graphql-tools.com/docs/resolvers/). 44 45```js 46cacheExchange({ 47 updates: { 48 Mutation: { 49 mutationField: (result, args, cache, info) => { 50 // ... 51 }, 52 }, 53 Subscription: { 54 subscriptionField: (result, args, cache, info) => { 55 // ... 56 }, 57 }, 58 }, 59}); 60``` 61 62An "updater" may be attached to a `Mutation` or `Subscription` field and accepts four positional 63arguments, which are the same as [the resolvers' arguments](./local-resolvers.md): 64 65- `result`: The full API result that's being written to the cache. Typically we'd want to 66 avoid coupling by only looking at the current field that the updater is attached to, but it's 67 worth noting that we can access any part of the result. 68- `args`: The arguments that the field has been called with, which will be replaced with an empty 69 object if the field hasn't been called with any arguments. 70- `cache`: The `cache` instance, which gives us access to methods allowing us to interact with the 71 local cache. Its full API can be found [in the API docs](../api/graphcache.md#cache). On this page 72 we use it frequently to read from and write to the cache. 73- `info`: This argument shouldn't be used frequently, but it contains running information about the 74 traversal of the query document. It allows us to make resolvers reusable or to retrieve 75 information about the entire query. Its full API can be found [in the API 76 docs](../api/graphcache.md#info). 77 78The cache updaters return value is disregarded (and typed as `void` in TypeScript), which makes any 79method that they call on the `cache` instance a side effect, which may trigger additional cache 80changes and updates all affected queries as we modify them. 81 82## Why do we need cache updates? 83 84When we’re designing a GraphQL schema well, we won’t need to write many cache updaters for 85Graphcache. 86 87For example, we may have a mutation to update a username on a `User`, which can trivially 88update the cache without us writing an updater because it resolves the `User`. 89 90```graphql 91query User($id: ID!) { 92 user(id: $id) { 93 __typename # "User" 94 id 95 username 96 } 97} 98 99mutation UpdateUsername($id: ID!, $username: String!) { 100 updateUser(id: $id, username: $username) { 101 __typename # "User" 102 id 103 username 104 } 105} 106``` 107 108In the above example, `Query.user` returns a `User`, which is then updated by a mutation on 109`Mutation.updateUser`. Since the mutation also queries the `User`, the updated username will 110automatically be applied by Graphcache. If the mutation field didn’t return a `User`, then this 111wouldn’t be possible, and while we can write an updater in Graphcache for it, we should consider 112this poor schema design. 113 114An updater instead becomes absolutely necessary when a mutation can’t reasonably return what has 115changed or when we can’t manually define a selection set that’d be even able to select all fields 116that may update. Some examples may include: 117 118- `Mutation.deleteUser`, since we’ll need to invalidate an entity 119- `Mutation.createUser`, since a list may now have to include a new entity 120- `Mutation.createBook`, since a given entity, e.g. `User` may have a field `User.books` that now 121 needs to be updated. 122 123In short, we may need to write a cache updater for any **relation** (i.e. link) that we can’t query 124via our GraphQL mutation directly, since there’ll be changes to our data that Graphcache won’t be 125able to see and store. 126 127In a later section on this page, [we’ll learn about the `cache.link` method.](#writing-links-individually) 128This method is used to update a field to point at a different entity. In other words, `cache.link` 129is used to update a relation from one entity field to one or more other child entities. 130This is the most common update we’ll need and it’s preferable to always try to use `cache.link`, 131unless we need to update a scalar. 132 133## Manually updating entities 134 135If a mutation field's result isn't returning the full entity it updates then it becomes impossible 136for Graphcache to update said entity automatically. For instance, we may have a mutation like the 137following: 138 139```graphql 140mutation UpdateTodo($todoId: ID!, $date: String!) { 141 updateTodoDate(id: $todoId, date: $date) 142} 143``` 144 145In this hypothetical case instead of `Mutation.updateDate` resolving to the full `Todo` object type 146it instead results in a scalar. This could be fixed by changing the `Mutation` in our API's schema 147to instead return the full `Todo` entity, which would allow us to run the mutation as such, which 148updates the `Todo` in our cache automatically: 149 150```graphql 151mutation UpdateTodo($todoId: ID!, $date: String!) { 152 updateTodoDate(id: $todoId, date: $date) { 153 ...Todo_date 154 } 155} 156 157fragment Todo_date on Todo { 158 id 159 updatedAt 160} 161``` 162 163However, if this isn't possible we can instead write an updater that updates our `Todo` entity 164manually by using the `cache.writeFragment` method: 165 166```js 167import { gql } from '@urql/core'; 168 169cacheExchange({ 170 updates: { 171 Mutation: { 172 updateTodoDate(_result, args, cache, _info) { 173 const fragment = gql` 174 fragment _ on Todo { 175 id 176 updatedAt 177 } 178 `; 179 180 cache.writeFragment(fragment, { id: args.id, updatedAt: args.date }); 181 }, 182 }, 183 }, 184}); 185``` 186 187The `cache.writeFragment` method is similar to the `cache.readFragment` method that we've seen [on 188the "Local Resolvers" page before](./local-resolvers.md#reading-a-fragment). Instead of reading data 189for a given fragment it instead writes data to the cache. 190 191> **Note:** In the above example, we've used 192> [the `gql` tag function](../api/core.md#gql) because `writeFragment` only accepts 193> GraphQL `DocumentNode`s as inputs, and not strings. 194 195### Cache Updates outside updaters 196 197Cache updates are **not** possible outside `updates`'s functions. If we attempt to store the `cache` 198in a variable and call its methods outside any `updates` functions (or functions, like `resolvers`) 199then Graphcache will throw an error. 200 201Methods like these cannot be called outside the `cacheExchange`'s `updates` functions, because 202all updates are isolated to be _reactive_ to mutations and subscription events. In Graphcache, 203out-of-band updates aren't permitted because the cache attempts to only represent the server's 204state. This limitation keeps the data of the cache true to the server data we receive from API 205results and makes its behaviour much more predictable. 206 207If we still manage to call any of the cache's methods outside its callbacks in its configuration, 208we will receive [a "(2) Invalid Cache Call" error](./errors.md#2-invalid-cache-call). 209 210### Updaters on arbitrary types 211 212Cache updates **may** be configured for arbitrary types and not just for `Mutation` or 213`Subscription` fields. However, this can potentially be **dangerous** and is an easy trap 214to fall into. It is allowed though because it allows for some nice tricks and workarounds. 215 216Given an updater on an arbitrary type, e.g. `Todo.author`, we can chain updates onto this field 217whenever it’s written. The updater can then be triggerd by Graphcache during _any_ operation; 218mutations, queries, and subscriptions. When this update is triggered, it allows us to add more 219arbitrary updates onto this field. 220 221> **Note:** If you’re looking to use this because you’re nesting mutations onto other object types, 222> e.g. `Mutation.author.updateName`, please consider changing your schema first before using this. 223> Namespacing mutations is not recommended and changes the execution order to be concurrent rather 224> than sequential when you use multiple nested mutation fields. 225 226## Updating lists or links 227 228Mutations that create new entities are pretty common, and it's not uncommon to attempt to update the 229cache when a mutation result for these "creation" mutations come back, since this avoids an 230additional roundtrip to our APIs. 231 232While it's possible for these mutations to return any affected entities that carry the lists as 233well, often these lists live on fields on or below the `Query` root type, which means that we'd be 234sending a rather large API result. For large amounts of pages this is especially infeasible. 235Instead, most schemas opt to instead just return the entity that's just been created: 236 237```graphql 238mutation NewTodo($text: String!) { 239 createTodo(id: $todoId, text: $text) { 240 id 241 text 242 } 243} 244``` 245 246If we have a corresponding field on `Query.todos` that contains all of our `Todo` entities then this 247means that we'll need to create an updater that automatically adds the `Todo` to our list: 248 249```js 250cacheExchange({ 251 updates: { 252 Mutation: { 253 createTodo(result, _args, cache, _info) { 254 const TodoList = gql` 255 { 256 todos { 257 id 258 } 259 } 260 `; 261 262 cache.updateQuery({ query: TodoList }, data => { 263 return { 264 ...data, 265 todos: [...data.todos, result.createTodo], 266 }; 267 }); 268 }, 269 }, 270 }, 271}); 272``` 273 274Here we use the `cache.updateQuery` method, which is similar to the [`cache.readQuery` method](./local-resolvers.md#reading-a-query) that 275we've seen on the "Local Resolvers" page before. 276 277This method accepts a callback, which will give us the `data` of the query, as read from the locally 278cached data, and we may return an updated version of this data. While we may want to instinctively 279opt for immutably copying and modifying this data, we're actually allowed to mutate it directly, 280since it's just a copy of the data that's been read by the cache. 281 282This `data` may also be `null` if the cache doesn't actually have enough locally cached information 283to fulfil the query. This is important because resolvers aren't actually applied to cache methods in 284updaters. All resolvers are ignored, so it becomes impossible to accidentally commit transformed data 285to our cache. We could safely add a resolver for `Todo.createdAt` and wouldn't have to worry about 286an updater accidentally writing it to the cache's internal data structure. 287 288### Writing links individually 289 290As long as we're only updating links (as in 'relations') then we may also use the [`cache.link` 291method](../api/graphcache.md#link). This method is the "write equivalent" of [the `cache.resolve` 292method, as seen on the "Local Resolvers" page before.](./local-resolvers.md#resolving-other-fields) 293 294We can use this method to update any relation in our cache, so the example above could also be 295rewritten to use `cache.link` and `cache.resolve` rather than `cache.updateQuery`. 296 297```js 298cacheExchange({ 299 updates: { 300 Mutation: { 301 createTodo(result, _args, cache, _info) { 302 const todos = cache.resolve('Query', 'todos'); 303 if (Array.isArray(todos)) { 304 cache.link('Query', 'todos', [...todos, result.createTodo]); 305 } 306 }, 307 }, 308 }, 309}); 310``` 311 312This method can be combined with more than just `cache.resolve`, for instance, it's a good fit with 313`cache.inspectFields`. However, when you're writing records (as in 'scalar' values) 314`cache.writeFragment` and `cache.updateQuery` are still the only methods that you can use. 315But since this kind of data is often written automatically by the normalized cache, often updating a 316link is the only modification we may want to make. 317 318## Updating many unknown links 319 320In the previous section we've seen how to update data, like a list, when a mutation result enters 321the cache. However, we've used a rather simple example when we've looked at a single list on a known 322field. 323 324In many schemas pagination is quite common, and when we for instance delete a todo then knowing the 325lists to update becomes unknowable. We cannot know ahead of time how many pages (and its variables) 326we've already accessed. This knowledge in fact _shouldn't_ be available to Graphcache. Querying the 327`Client` is an entirely separate concern that's often colocated with some part of our 328UI code. 329 330```graphql 331mutation RemoveTodo($id: ID!) { 332 removeTodo(id: $id) 333} 334``` 335 336Suppose we have the above mutation, which deletes a `Todo` entity by its ID. Our app may query a list 337of these items over many pages with separate queries being sent to our API, which makes it hard to 338know the fields that should be checked: 339 340```graphql 341query PaginatedTodos($skip: Int) { 342 todos(skip: $skip) { 343 id 344 text 345 } 346} 347``` 348 349Instead, we can **introspect an entity's fields** to find the fields we may want to update 350dynamically. This is possible thanks to [the `cache.inspectFields` 351method](../api/graphcache.md#inspectfields). This method accepts a key, or a keyable entity like the 352`cache.keyOfEntity` method that [we've seen on the "Local Resolvers" 353page](./local-resolvers.md#resolving-by-keys) or the `cache.resolve` method's first argument. 354 355```js 356cacheExchange({ 357 updates: { 358 Mutation: { 359 removeTodo(_result, args, cache, _info) { 360 const TodoList = gql` 361 query (skip: $skip) { 362 todos(skip: $skip) { id } 363 } 364 `; 365 366 const fields = cache 367 .inspectFields('Query') 368 .filter(field => field.fieldName === 'todos') 369 .forEach(field => { 370 cache.updateQuery( 371 { 372 query: TodoList, 373 variables: { skip: field.arguments.skip }, 374 }, 375 data => { 376 data.todos = data.todos.filter(todo => todo.id !== args.id); 377 return data; 378 } 379 ); 380 }); 381 }, 382 }, 383 }, 384}); 385``` 386 387To implement an updater for our example's `removeTodo` mutation field we may use the 388`cache.inspectFields('Query')` method to retrieve a list of all fields on the `Query` root entity. 389This list will contain all known fields on the `"Query"` entity. Each field is described as an 390object with three properties: 391 392- `fieldName`: The field's name; in this case we're filtering for all `todos` listing fields. 393- `arguments`: The arguments for the given field, since each field that accepts arguments can be 394 accessed multiple times with different arguments. In this example we're looking at 395 `arguments.skip` to find all unique pages. 396- `fieldKey`: This is the field's key, which can come in useful to retrieve a field using 397 `cache.resolve(entityKey, fieldKey)` to prevent the arguments from having to be stringified 398 repeatedly. 399 400To summarise, we filter the list of fields in our example down to only the `todos` fields and 401iterate over each of our `arguments` for the `todos` field to filter all lists to remove the `Todo` 402from them. 403 404### Inspecting arbitrary entities 405 406We're not required to only inspecting fields on the `Query` root entity. Instead, we can inspect 407fields on any entity by passing a different partial, keyable entity or key to `cache.inspectFields`. 408 409For instance, if we had a `Todo` entity and wanted to get all of its known fields then we could pass 410in a partial `Todo` entity just as well: 411 412```js 413cache.inspectFields({ 414 __typename: 'Todo', 415 id: args.id, 416}); 417``` 418 419## Invalidating Entities 420 421Admittedly, it's sometimes almost impossible to write updaters for all mutations. It's often even 422hard to predict what our APIs may do when they receive a mutation. An update of an entity may change 423the sorting of a list, or remove an item from a list in a way we can't predict, since we don't have 424access to a full database to run the API locally. 425 426In cases like these it may be advisable to trigger a refetch instead and let the cache update itself 427by sending queries that have invalidated data associated to them to our API again. This process is 428called **invalidation** since it removes data from Graphcache's locally cached data. 429 430We may use the cache's [`cache.invalidate` method](../api/graphcache.md#invalidate) to either 431invalidate entire entities or individual fields. It has the same signature as [the `cache.resolve` 432method](../api/graphcache.md#resolve), which we've already seen [on the "Local Resolvers" page as 433well](./local-resolvers.md#resolving-other-fields). We can simplify the previous update we've written 434with a call to `cache.invalidate`: 435 436```js 437cacheExchange({ 438 updates: { 439 Mutation: { 440 removeTodo(_result, args, cache, _info) { 441 cache.invalidate({ 442 __typename: 'Todo', 443 id: args.id, 444 }); 445 }, 446 }, 447 }, 448}); 449``` 450 451Like any other cache update, this will cause all queries that use this `Todo` entity to be updated 452against the cache. Since we've invalidated the `Todo` item they're using these queries will be 453refetched and sent to our API. 454 455If we're using ["Schema Awareness"](./schema-awareness.md) then these queries' results may actually 456be temporarily updated with a partial result, but in general we should observe that queries with 457data that has been invalidated will be refetched as some of their data isn't cached anymore. 458 459### Invalidating individual fields 460 461We may also want to only invalidate individual fields, since maybe not all queries have to be 462immediately updated. We can pass a field (and optional arguments) to the `cache.invalidate` method 463as well to only invalidate a single field. 464 465For instance, we can use this to invalidate our lists instead of invalidating the entity itself. 466This can be useful if we know that modifying an entity will cause our list to be sorted differently, 467for instance. 468 469```js 470cacheExchange({ 471 updates: { 472 Mutation: { 473 updateTodo(_result, args, cache, _info) { 474 const key = 'Query'; 475 const fields = cache 476 .inspectFields(key) 477 .filter(field => field.fieldName === 'todos') 478 .forEach(field => { 479 cache.invalidate(key, field.fieldKey); 480 // or alternatively: 481 cache.invalidate(key, field.fieldName, field.arguments); 482 }); 483 }, 484 }, 485 }, 486}); 487``` 488 489In this example we've attached an updater to a `Mutation.updateTodo` field. We react to this 490mutation by enumerating all `todos` listing fields using `cache.inspectFields` and targetedly 491invalidate only these fields, which causes all queries using these listing fields to be refetched. 492 493### Invalidating a type 494 495We can also invalidate all the entities of a given type, this could be handy in the case of a 496list update or when you aren't sure what entity is affected. 497 498This can be done by only passing the relevant `__typename` to the `invalidate` function. 499 500```js 501cacheExchange({ 502 updates: { 503 Mutation: { 504 deleteTodo(_result, args, cache, _info) { 505 cache.invalidate('Todo'); 506 }, 507 }, 508 }, 509}); 510``` 511 512## Optimistic updates 513 514If we know what result a mutation may return, why wait for the GraphQL API to fulfill our mutations? 515 516In addition to the `updates` configuration, we can also pass an `optimistic` option to the 517`cacheExchange`. This option is a factory function that allows us to create a "virtual" result for a 518mutation. This temporary result can be applied immediately to the cache to give our users the 519illusion that mutations were executed immediately, which is a great method to reduce waiting time 520and to make our apps feel snappier. 521This technique is often used with one-off mutations that are assumed to succeed, like starring a 522repository, or liking a tweet. In such cases it's often desirable to make the interaction feel 523as instant as possible. 524 525The `optimistic` configuration is similar to our `resolvers` or `updates` configuration, except that 526it only receives a single map for mutation fields. We can attach optimistic functions to any 527mutation field to make it generate an optimistic that is applied to the cache while the `Client` 528waits for a response from our API. An "optimistic" function accepts three positional arguments, 529which are the same as the resolvers' or updaters' arguments, except for the first one: 530 531The `optimistic` functions receive the same arguments as `updates` functions, except for `parent`, 532since we don't have any server data to work with: 533 534- `args`: The arguments that the field has been called with, which will be replaced with an empty 535 object if the field hasn't been called with any arguments. 536- `cache`: The `cache` instance, which gives us access to methods allowing us to interact with the 537 local cache. Its full API can be found [in the API docs](../api/graphcache.md#cache). On this page 538 we use it frequently to read from and write to the cache. 539- `info`: This argument shouldn't be used frequently, but it contains running information about the 540 traversal of the query document. It allows us to make resolvers reusable or to retrieve 541 information about the entire query. Its full API can be found [in the API 542 docs](../api/graphcache.md#info). 543 544The usual `parent` argument isn't present since optimistic functions don't have any server data to 545handle or deal with and instead create this data. When a mutation is run that contains one or more 546optimistic mutation fields, Graphcache picks these up and generates immediate changes, which it 547applies to the cache. The `resolvers` functions also trigger as if the results were real server 548results. 549 550This modification is temporary. Once a result from the API comes back it's reverted, which leaves us 551in a state where the cache can apply the "real" result to the cache. 552 553> Note: While optimistic mutations are waiting for results from the API all queries that may alter 554> our optimistic data are paused (or rather queued up) and all optimistic mutations will be reverted 555> at the same time. This means that optimistic results can stack but will never accidentally be 556> confused with "real" data in your configuration. 557 558In the following example we assume that we'd like to implement an optimistic result for a 559`favoriteTodo` mutation, like such: 560 561```graphql 562mutation FavoriteTodo(id: $id) { 563 favoriteTodo(id: $id) { 564 id 565 favorite 566 updatedAt 567 } 568} 569``` 570 571The mutation is rather simple and all we have to do is create a function 572that imitates the result that the API is assumed to send back: 573 574```js 575const cache = cacheExchange({ 576 optimistic: { 577 favoriteTodo(args, cache, info) { 578 return { 579 __typename: 'Todo', 580 id: args.id, 581 favorite: true, 582 }; 583 }, 584 }, 585}); 586``` 587 588This optimistic mutation will be applied to the cache. If any `updates` configuration exists for 589`Mutation.favoriteTodo` then it will be executed using the optimistic result. 590Once the mutation result comes back from our API this temporary change will be rolled back and 591discarded. 592 593In the above example optimistic mutation function we also see that `updatedAt` is not present in our 594optimistic return value. That’s because we don’t always have to (or can) match our mutations’ 595selection sets perfectly. Instead, Graphcache will skip over fields and use cached fields for any we 596leave out. This can even work on nested entities and fields. 597 598However, leaving out fields can sometimes cause the optimistic update to not apply when we 599accidentally cause any query that needs to update accordingly to only be partially cached. In other 600words, if our optimistic updates cause a cache miss, we won’t see them being applied. 601 602Sometimes we may need to apply optimistic updates to fields that accept arguments. For instance, our 603`favorite` field may have a date cut-off: 604 605```graphql 606mutation FavoriteTodo(id: $id) { 607 favoriteTodo(id: $id) { 608 id 609 favorite(since: ONE_MONTH_AGO) 610 updatedAt 611 } 612} 613``` 614 615To solve this, we can return a method on the optimistic result our `optimistic` update function 616returns: 617 618```js 619const cache = cacheExchange({ 620 optimistic: { 621 favoriteTodo(args, cache, info) { 622 return { 623 __typename: 'Todo', 624 id: args.id, 625 favorite(_args, cache, info) { 626 return true; 627 }, 628 }, 629 }, 630 }, 631}); 632``` 633 634The function signature and arguments it receives is identical to the toplevel optimistic function 635you define, and is basically like a nested optimistic function. 636 637### Variables for Optimistic Updates 638 639Sometimes it's not possible for us to retrieve all data that an optimistic update requires to create 640a "fake result" from the cache or from all existing variables. 641 642This is why Graphcache allows for a small escape hatch for these scenarios, which allows us to access 643additional variables, which we may want to pass from our UI code to the mutation. For instance, given 644a mutation like the following we may add more variables than the mutation specifies: 645 646```graphql 647mutation UpdateTodo($id: ID!, $text: ID!) { 648 updateTodo(id: $id, text: $text) { 649 id 650 text 651 } 652} 653``` 654 655In the above mutation we've only defined an `$id` and `$text` variable. Graphcache typically filters 656variables using our query document definitions, which means that our API will never receive any 657variables other than the ones we've defined. 658 659However, we're able to pass additional variables to our mutation, e.g. `{ extra }`, and since 660`$extra` isn't defined it will be filtered once the mutation is sent to the API. An optimistic 661mutation however will still be able to access this variable, like so: 662 663```js 664cacheExchange({ 665 updates: { 666 Mutation: { 667 updateTodo(_result, _args, _cache, info) { 668 const extraVariable = info.variables.extra; 669 }, 670 }, 671 }, 672}); 673``` 674 675### Reading on 676 677[On the next page we'll learn about "Schema Awareness".](./schema-awareness.md)