Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.
1--- 2title: Local Resolvers 3order: 2 4--- 5 6# Local Resolvers 7 8Previously, we've learned about local resolvers [on the "Normalized Caching" 9page](./normalized-caching.md#manually-resolving-entities). They allow us to change the data that 10Graphcache reads as it queries against its local cache, return links that would otherwise not be 11cached, or even transform scalar records on the fly. 12 13The `resolvers` option on `cacheExchange` accepts a map of types with a nested map of fields, which 14means that we can add local resolvers to any field of any type. For example: 15 16```js 17cacheExchange({ 18 resolvers: { 19 Todo: { 20 updatedAt: parent => new Date(parent.updatedAt), 21 }, 22 }, 23}); 24``` 25 26In the above example, what Graphcache does when it encounters the `updatedAt` field on `Todo` types. 27Similarly to how Graphcache knows [how to generate 28keys](./normalized-caching.md#custom-keys-and-non-keyable-entities) and looks up our custom `keys` 29configuration functions per `__typename`, it also uses our `resolvers` configuration on each field 30it queries from its locally cached data. 31 32A local resolver function in Graphcache has a similar signature to [GraphQL.js' resolvers on the 33server-side](https://www.graphql-tools.com/docs/resolvers/), so their shape should look familiar to 34us. 35 36```js 37{ 38 TypeName: { 39 fieldName: (parent, args, cache, info) => { 40 return null; // new value 41 }, 42 }, 43} 44``` 45 46A resolver may be attached to any type's field and accepts four positional arguments: 47 48- `parent`: The object on which the field will be added to, which contains the data as it's being 49 queried. It will contain the current field's raw value if it's a scalar, which allows us to 50 manipulate scalar values, like `parent.updatedAt` in the previous example. 51- `args`: The arguments that the field is being called with, which will be replaced with an empty 52 object if the field hasn't been called with any arguments. For example, if the field is queried as 53 `name(capitalize: true)` then `args` would be `{ capitalize: true }`. 54- `cache`: Unlike in GraphQL.js this will not be the context, but a `cache` instance, which gives us 55 access to methods allowing us to interact with the local cache. Its full API can be found [in the 56 API docs](../api/graphcache.md#cache). 57- `info`: This argument shouldn't be used frequently, but it contains running information about the 58 traversal of the query document. It allows us to make resolvers reusable or to retrieve 59 information about the entire query. Its full API can be found [in the API 60 docs](../api/graphcache.md#info). 61 62The local resolvers may return any value that fits the query document's shape, however we must 63ensure that what we return matches the types of our schema. It, for instance, isn't possible to turn a 64record field into a link, i.e. replace a scalar with an entity. Instead, local resolvers are useful 65to transform records, like dates in our previous example, or to imitate server-side logic to allow 66Graphcache to retrieve more data from its cache without sending a query to our API. 67 68Furthermore, while we see on this page that we get access to methods like `cache.resolve` and other 69methods to read from our cache, only ["Cache Updates"](./cache-updates.md) get to write and change 70the cache. If you call `cache.updateQuery`, `cache.writeFragment`, or `cache.link` in resolvers, 71you‘ll get an error, since it‘s not possible to update the cache while reading from it. 72 73When writing a resolver you’ll mostly use `cache.resolve`, which can be chained, to read field 74values from the cache. When a field points to another entity we may get a key, but resolvers are 75allowed to return keys or partial entities containing keys. 76 77> **Note:** This essentially means that resolvers can return either scalar values for fields without 78> selection sets, and either partial entities or keys for fields with selection sets, i.e. 79> links / relations. When we return `null`, this will be interpreted a the literal GraphQL Null scalar, 80> while returning `undefined` will cause a cache miss. 81 82## Transforming Records 83 84As we've explored in the ["Normalized Caching" page's section on 85records](./normalized-caching.md#storing-normalized-data), "records" are scalars and any fields in 86your query without selection sets. This could be a field with a string value, number, or any other 87field that resolves to a [scalar type](https://graphql.org/learn/schema/#scalar-types) rather than 88another entity i.e. object type. 89 90At the beginning of this page we've already seen an example of a local resolver that we've attached 91to a record field where we've added a resolver to a `Todo.updatedAt` field: 92 93```js 94cacheExchange({ 95 resolvers: { 96 Todo: { 97 updatedAt: parent => new Date(parent.updatedAt), 98 }, 99 }, 100}); 101``` 102 103A query that contains this field may look like `{ todo { updatedAt } }`, which clearly shows us that 104this field is a scalar since it doesn't have any selection set on the `updatedAt` field. In our 105example, we access this field's value and parse it as a `new Date()`. 106 107This shows us that it doesn't matter for scalar fields what kind of value we return. We may parse 108strings into more granular JS-native objects or replace values entirely. 109 110We may also run into situations where we'd like to generalise the resolver and not make it dependent 111on the exact field it's being attached to. In these cases, the [`info` 112object](../api/graphcache.md#info) can be very helpful as it provides us information about the 113current query traversal, and the part of the query document the cache is processing. The 114`info.fieldName` property is one of these properties and lets us know the field that the resolver is 115operating on. Hence, we can create a reusable resolver like so: 116 117```js 118const transformToDate = (parent, _args, _cache, info) => new Date(parent[info.fieldName]); 119 120cacheExchange({ 121 resolvers: { 122 Todo: { updatedAt: transformToDate }, 123 }, 124}); 125``` 126 127The resolver is now much more reusable, which is particularly handy if we're creating resolvers that 128we'd like to apply to multiple fields. The [`info` object has several more 129fields](../api/graphcache.md#info) that are all similarly useful to abstract our resolvers. 130 131We also haven't seen yet how to handle a field's arguments. 132If we have a field that accepts arguments we can use those as well as they're passed to us with the 133second argument of a resolver: 134 135```js 136cacheExchange({ 137 resolvers: { 138 Todo: { 139 text: (parent, args) => { 140 return args.capitalize && parent.text ? parent.text.toUpperCase() : parent.text; 141 }, 142 }, 143 }, 144}); 145``` 146 147This is actually unlikely to be of use with records and scalar values as our API will have to be 148able to use these arguments just as well. In other words, while you may be able to pass any 149arguments to a field in your query, your GraphQL API's schema must accept these arguments in the 150first place. However, this is still useful if we're trying to imitate what the API is doing, which 151will become more relevant in the following examples and sections. 152 153## Resolving Entities 154 155We've already briefly seen that resolvers can be used to replace a link in Graphcache's local data 156on the ["Normalized Caching" page](./normalized-caching.md#manually-resolving-entities). 157 158Given that Graphcache [stores entities in a normalized data 159structure](./normalized-caching.md#storing-normalized-data) there may be multiple fields on a given 160schema that can be used to get to the same entity. For instance, the schema may allow for the same 161entity to be looked up by an ID while this entity may also appear somewhere else in a list or on an 162entirely different field. 163 164When links (or relations) like these are cached by Graphcache it is able to look up the entities 165automatically, e.g. if we've sent a `{ todo(id: 1) { id } }` query to our API once then Graphcache 166will have seen that this field leads to the entity it returns and can query it automatically from 167its cache. 168 169However, if we have a list like `{ todos { id } }` we may have seen and cached a specific entity, 170but as we browse the app and query for `{ todo(id: 1) { id } }`, Graphcache isn't able to 171automatically find this entity even if it has cached it already and will send a request to our API. 172 173In many cases we can create a local resolvers to instead tell the cache where to look for a specific 174entity by returning partial information for it. Any resolver on a relational field, meaning any 175field that links to an object type (or a list of object types) in the schema, may return a partial 176entity that tells the cache how to resolve it. Hence, we're able to implement a resolver for the 177previously shown `todo(id: $id)` field as such: 178 179```js 180cacheExchange({ 181 resolvers: { 182 Query: { 183 todo: (_, args) => ({ __typename: 'Todo', id: args.id }), 184 }, 185 }, 186}); 187``` 188 189The `__typename` field is required. Graphcache will [use its keying 190logic](./normalized-caching.md#custom-keys-and-non-keyable-entities), and your custom `keys` 191configuration to generate a key for this entity and will then be able to look this entity up in its 192local cache. As with regular queries, the resolver is known to return a link since the `todo(id: $id) { id }` will be used with a selection set, querying fields on the entity. 193 194### Resolving by keys 195 196Resolvers can also directly return keys. We've previously learned [on the "Normalized Caching" 197page](./normalized-caching.md#custom-keys-and-non-keyable-entities) that the key for our example above 198would look something like `"Todo:1"` for `todo(id: 1)`. While it isn't advisable to create keys 199manually in your resolvers, if you returned a key directly this would still work. 200 201Essentially, returning `{ __typename, id }` may sometimes be the same as returning the key manually. 202The `cache` that we receive as an argument on resolvers has a method for this logic, [the 203`cache.keyOfEntity` method](../api/graphcache.md#keyofentity). 204 205While it doesn't make much sense in this case, our example can be rewritten as: 206 207```js 208cacheExchange({ 209 resolvers: { 210 Query: { 211 todo: (_, args, cache) => cache.keyOfEntity({ __typename: 'Todo', id: args.id }), 212 }, 213 }, 214}); 215``` 216 217And while it's not advisable to create keys ourselves, the resolvers' `cache` and `info` arguments 218give us ample opportunities to use and pass around keys. 219 220One example is the `info.parentKey` property. This property [on the `info` 221object](../api/graphcache.md#info) will always be set to the key of the entity that the resolver is 222currently run on. For instance, for the above resolver it may be `"Query"`, for for a resolver on 223`Todo.updatedAt` it may be `"Todo:1"`. 224 225## Resolving other fields 226 227In the above two examples we've seen how a resolver can replace Graphcache's logic, which usually 228reads links and records only from its locally cached data. We've seen how a field on a record can 229use `parent[fieldName]` to access its cached record value and transform it and how a resolver for a 230link can return a partial entity [or a key](#resolving-by-keys). 231 232However sometimes we'll need to resolve data from other fields in our resolvers. 233 234> **Note:** For records, if the other field is on the same `parent` entity, it may seem logical to access it on 235> `parent[otherFieldName]` as well, however the `parent` object will only be sparsely populated with 236> fields that the cache has already queried prior to reaching the resolver. 237> In the previous example, where we've created a resolver for `Todo.updatedAt` and accessed 238> `parent.updatedAt` to transform its value the `parent.updatedAt` field is essentially a shortcut 239> that allows us to get to the record quickly. 240 241Instead we can use [the `cache.resolve` method](../api/graphcache.md#resolve). This method 242allows us to access Graphcache's cached data directly. It is used to resolve records or links on any 243given entity and accepts three arguments: 244 245- `entity`: This is the entity on which we'd like to access a field. We may either pass a keyable, 246 partial entity, e.g. `{ __typename: 'Todo', id: 1 }` or a key. It takes the same inputs as [the 247 `cache.keyOfEntity` method](../api/graphcache.md#keyofentity), which we've seen earlier in the 248 ["Resolving by keys" section](#resolving-by-keys). It also accepts `null` which causes it to 249 return `null`, which is useful for chaining multiple `resolve` calls for deeply accessing a field. 250- `fieldName`: This is the field's name we'd like to access. If we're looking for the record on 251 `Todo.updatedAt` we would pass `"updatedAt"` and would receive the record value for this field. If 252 we pass a field that is a _link_ to another entity then we'd pass that field's name (e.g. 253 `"author"` for `Todo.author`) and `cache.resolve` will return a key instead of a record value. 254- `fieldArgs`: Optionally, as the third argument we may pass the field's arguments, e.g. `{ id: 1 }` 255 if we're trying to access `todo(id: 1)` for instance. 256 257This means that we can rewrite our original `Todo.updatedAt` example as follows, if we'd like to 258avoid using the `parent[fieldName]` shortcut: 259 260```js 261cacheExchange({ 262 resolvers: { 263 Todo: { 264 updatedAt: (parent, _args, cache) => new Date(cache.resolve(parent, 'updatedAt')), 265 }, 266 }, 267}); 268``` 269 270When we call `cache.resolve(parent, "updatedAt")`, the cache will look up the `"updatedAt"` field on 271the `parent` entity, i.e. on the current `Todo` entity. 272 273> **Note:** We've also previously learned that `parent` may not contain all fields that the entity may have and 274> may hence be missing its keyable fields, like `id`, so why does this then work? 275> It works because `cache.resolve(parent)` is a shortcut for `cache.resolve(info.parentKey)`. 276 277Like the `info.fieldName` property `info.parentKey` gives us information about the current state of 278Graphcache's query operation. In this case, `info.parentKey` tells us what the parent's key is. 279However, since `cache.resolve(parent)` is much more intuitive we can write that instead since this 280is a supported shortcut. 281 282From this follows that we may also use `cache.resolve` to access other fields. Let's suppose we'd 283want `updatedAt` to default to the entity's `createdAt` field when it's actually `null`. In such a 284case we could write a resolver like so: 285 286```js 287cacheExchange({ 288 resolvers: { 289 Todo: { 290 updatedAt: (parent, _args, cache) => parent.updatedAt || cache.resolve(parent, 'createdAt'), 291 }, 292 }, 293}); 294``` 295 296As we can see, we're effortlessly able to access other records from the cache, provided these fields 297are actually cached. If they aren't `cache.resolve` will return `null` instead. 298 299Beyond records, we're also able to resolve links and hence jump to records from another entity. 300Let's suppose we have an `author { id, createdAt }` field on the `Todo` and would like 301`Todo.createdAt` to simply copy the author's `createdAt` field. We can chain `cache.resolve` calls 302to get to this value: 303 304```js 305cacheExchange({ 306 resolvers: { 307 Todo: { 308 createdAt: (parent, _args, cache) => 309 cache.resolve(cache.resolve(parent, 'author') /* "Author:1" */, 'createdAt'), 310 }, 311 }, 312}); 313``` 314 315The return value of `cache.resolve` changes depending on what data the cache has stored. While it 316may return records for fields without selection sets, in other cases it may give you the key of 317other entities ("links") instead. It can even give you arrays of keys or records when the field's 318value contains a list. 319 320When a value is not present in the cache, `cache.resolve` will instead return `undefined` to signal 321that a value is uncached. Similarly, a resolver may return `undefined` to tell Graphcache that the 322field isn’t cached and that a call to the API is necessary. 323 324`cache.resolve` is a pretty flexible method that allows us to access arbitrary values from our cache, 325however, we have to be careful about what value will be resolved by it, since the cache can't know 326itself what type of value it may return. 327 328The last trick this method allows you to apply is to access arbitrary fields on the root `Query` 329type. If we call `cache.resolve("Query", ...)` then we're also able to access arbitrary fields 330starting from the root `Query` of the cached data. (If you're using [Schema 331Awareness](./schema-awareness.md) the name `"Query"` may vary for you depending on your schema.) 332We're not constrained to accessing fields on the `parent` of a resolver but can also attempt to 333break out and access fields on any other entity we know of. 334 335## Resolving Partial Data 336 337Local resolvers also allow for more advanced use-cases when it comes to links and object types. 338Previously we've seen how a resolver is able to link up a given field to an entity, which causes 339this field to resolve an entity directly instead of it being checked against any cached links: 340 341```js 342cacheExchange({ 343 resolvers: { 344 Query: { 345 todo: (_, args) => ({ __typename: 'Todo', id: args.id }), 346 }, 347 }, 348}); 349``` 350 351In this example, while `__typename` and `id` are required to make this entity keyable, we're also 352able to add on more fields to this object to override values later on in our selection. 353 354For instance, we can write a resolver that links `Query.todo` directly to our `Todo` entity but also 355only updates the `createdAt` field directly in the same resolver, if it is indeed accessed via the 356`Query.todo` field: 357 358```js 359cacheExchange({ 360 resolvers: { 361 Query: { 362 todo: (_, args) => ({ 363 __typename: 'Todo', 364 id: args.id, 365 createdAt: new Date().toString(), 366 }), 367 }, 368 }, 369}); 370``` 371 372Here we've replaced the `createdAt` value of the `Todo` when it's accessed via this manual resolver. 373If it was accessed someplace else, for instance via a `Query.todos` listing field, this override 374wouldn't apply. 375 376We can even apply overrides to nested fields, which helps us to create complex resolvers for other 377use cases like pagination. 378 379[Read more on the topic of "Pagination" in the section below.](#pagination) 380 381## Computed Queries 382 383We've now seen how the `cache` has several powerful methods, like [the `cache.resolve` 384method](../api/graphcache.md#resolve), which allow us to access any data in the cache while writing 385resolvers for individual fields. 386 387Additionally the cache has more methods that allow us to access more data at a time, like 388`cache.readQuery` and `cache.readFragment`. 389 390### Reading a query 391 392At any point, the `cache` allows us to read entirely separate queries in our resolvers, which starts 393a separate virtual operation in our resolvers. When we call `cache.readQuery` with a query and 394variables we can execute an entirely new GraphQL query against our cached data: 395 396```js 397import { gql } from '@urql/core'; 398import { cacheExchange } from '@urql/exchange-graphcache'; 399 400const cache = cacheExchange({ 401 updates: { 402 Mutation: { 403 addTodo: (result, args, cache) => { 404 const data = cache.readQuery({ query: Todos, variables: { from: 0, limit: 10 } }); 405 }, 406 }, 407 }, 408}); 409``` 410 411This way we'll get the stored data for the `TodosQuery` for the given `variables`. 412 413[Read more about `cache.readQuery` in the Graphcache API docs.](../api/graphcache.md#readquery) 414 415### Reading a fragment 416 417The store also allows us to read a fragment for any given entity. The `cache.readFragment` method 418accepts a `fragment` and an `id`. This looks like the following. 419 420```js 421import { gql } from '@urql/core'; 422import { cacheExchange } from '@urql/exchange-graphcache'; 423 424const cache = cacheExchange({ 425 resolvers: { 426 Query: { 427 Todo: (parent, args, cache) => { 428 return cache.readFragment( 429 gql` 430 fragment _ on Todo { 431 id 432 text 433 } 434 `, 435 { id: 1 } 436 ); 437 }, 438 }, 439 }, 440}); 441``` 442 443> **Note:** In the above example, we've used 444> [the `gql` tag function](../api/core.md#gql) because `readFragment` only accepts 445> GraphQL `DocumentNode`s as inputs, and not strings. 446 447This way we'll read the entire fragment that we've passed for the `Todo` for the given key, in this 448case `{ id: 1 }`. 449 450[Read more about `cache.readFragment` in the Graphcache API docs.](../api/graphcache.md#readfragment) 451 452### Cache methods outside of `resolvers` 453 454The cache read methods are not possible outside of GraphQL operations. This means these methods will 455be limited to the different `Graphcache` configuration methods. 456 457## Living with limitations of Local Resolvers 458 459Local Resolvers are powerful tools using which we can tell Graphcache what to do with a certain 460field beyond using results it’s seen on prior API results. However, it’s limitations come from this 461very intention they were made for. 462 463Resolvers are meant to augment Graphcache and teach it what to do with some fields. Sometimes this 464is trivial and simple (like most examples on this page), but other times, fields are incredibly 465complex to reproduce and hence resolvers become more complex. 466 467This section is not exhaustive, but documents some of the more commonly asked for features of 468resolvers. However, beyond the cases listed below, resolvers are limited and: 469 470- can't manipulate or see other fields on the current entity, or fields above it. 471- can't update the cache (they're only “computations” but don't change the cache) 472- can't change the query document that's sent to the API 473 474### Writing reusable resolvers 475 476As we've seen before in the ["Transforming Records" section above](#transforming-records), we can 477write generic resolvers by using the fourth argument that resolvers receive, the `ResolveInfo` 478object. 479 480This `info` object gives our resolvers some context on where they’re being executed and gives it 481information about the current field and its surroundings. 482 483For instance, while Graphcache has a convenience helper to access a current record on the parent 484object for scalar values, it doesn't for links. Hence, if we're trying to read relationships we have 485to use `cache.resolve`. 486 487```js 488cacheExchange({ 489 resolvers: { 490 Todo: { 491 // This works: 492 updatedAt: parent => parent.updatedAt, 493 // This won't work: 494 author: parent => parent.author, 495 }, 496 }, 497}); 498``` 499 500The `info` object actually gives us two ways of accessing the original field's value: 501 502```js 503const resolver = (parent, args, cache, info) => { 504 // This is the full version 505 const original = cache.resolve(info.parentKey, info.fieldName, args); 506 // But we can replace `info.parentKey` with `parent` as a shortcut 507 const original = cache.resolve(parent, info.fieldName, args); 508 // And we can also avoid re-using arguments by using `fieldKey` 509 const original = cache.resolve(parent, info.fieldKey); 510}; 511``` 512 513Apart from telling us how to access the originally cached field value, we can also get more 514information from `info` about our field. For instance, we can: 515 516- Read the current field's name using `info.fieldName` 517- Read the current field's key using `info.parentFieldKey` 518- Read the current parent entity's key using `info.parentKey` 519- Read the current parent entity's typename using `info.parentTypename` 520- Access the current operation's raw variables using `info.variables` 521- Access the current operation's raw fragments using `info.fragments` 522 523### Causing cache misses and partial misses 524 525When we write resolvers we provide Graphcache with a value for the current field, or rather with 526"behavior", that it will execute no matter whether this field is also cached or not. 527 528This means that, unless our resolver returns `undefined`, if the query doesn't have any other cache 529misses, Graphcache will consider the field a cache hit and will, unless other cache misses occur, 530not make a network request. 531 532> **Note:** An exception for this is [Schema Awareness](./schema-awareness.md), which can 533> automatically cause partial cache misses. 534 535However, sometimes we may want a resolver to return a result, while still sending a GraphQL API 536request in the background to update our resolver’s values. 537 538To achieve this we can update the `info.partial` field. 539 540```js 541cacheExchange({ 542 resolvers: { 543 Todo: { 544 author(parent, args, cache, info) { 545 const author = cache.resolve(parent, info.fieldKey); 546 if (author === null) { 547 info.partial = true; 548 } 549 return author; 550 }, 551 }, 552 }, 553}); 554``` 555 556Suppose we have a field that our GraphQL schema _sometimes_ returns a `null` value for, but that may 557be upated with a value in the future. In the above example, we wrote a resolver that sets 558`info.partial = true` if a field’s value is `null`. This causes Graphcache to consider the result 559“partial and stale” and will cause it to make a background request to the API, while still 560delivering the outdated result. 561 562### Conditionally applying resolvers 563 564We may not always want a resolver to be used. While sometimes this can be dangerous (if your 565resolver affects the shape and types of your fields), in other cases this is necessary. 566For instance, if your resolver handles infinite-scroll pagination, like the examples [in the next 567section](#pagination), then you may not always want to apply this resolver. 568 569For this reason, Graphcache also supports [“local directives”, which are introduced on the next docs 570page.](./local-directives.md) 571 572## Pagination 573 574`Graphcache` offers some preset `resolvers` to help us out with endless scrolling pagination, also 575known as "infinite pagination". It comes with two more advanced but generalised resolvers that can 576be applied to two specific pagination use-cases. 577 578They're not meant to implement infinite pagination for _any app_, instead they're useful when we'd 579like to add infinite pagination to an app quickly to try it out or if we're unable to replace it 580with separate components per page in environments like React Native, where a `FlatList` would 581require a flat, infinite list of items. 582 583> **Note:** If you don't need a flat array of results, you can also achieve infinite pagination 584> with only UI code. [You can find a code example of UI infinite pagination in our example folder.](https://github.com/urql-graphql/urql/tree/main/examples/with-pagination) 585 586[You can find a code example of infinite pagination with Graphcahce in our example folder.](https://github.com/urql-graphql/urql/tree/main/examples/with-graphcache-pagination). 587Please keep in mind that this patterns has some limitations when you're handling cache updates. 588Deleting old pages from the cache selectively may be difficult, so the UI pattern in the above 589note is preferred. 590 591### Simple Pagination 592 593Given we have a schema that uses some form of `offset` and `limit` based pagination, we can use the 594`simplePagination` exported from `@urql/exchange-graphcache/extras` to achieve an endless scroller. 595 596This helper will concatenate all queries performed to one long data structure. 597 598```js 599import { cacheExchange } from '@urql/exchange-graphcache'; 600import { simplePagination } from '@urql/exchange-graphcache/extras'; 601 602const cache = cacheExchange({ 603 resolvers: { 604 Query: { 605 todos: simplePagination(), 606 }, 607 }, 608}); 609``` 610 611This form of pagination accepts an object as an argument, we can specify two 612options in here `limitArgument` and `offsetArgument` these will default to `limit` 613and `skip` respectively. This way we can use the keywords that are in our queries. 614 615We may also add the `mergeMode` option, which defaults to `'after'` and can otherwise 616be set to `'before'`. This will handle in which order pages are merged when paginating. 617The default `after` mode assumes that pages that come in last should be merged 618_after_ the first pages. The `'before'` mode assumes that pages that come in last 619should be merged _before_ the first pages, which can be helpful in a reverse 620endless scroller (E.g. Chat App). 621 622Example series of requests: 623 624``` 625// An example where mergeMode: after works better 626skip: 0, limit: 3 => 1, 2, 3 627skip: 3, limit: 3 => 4, 5, 6 628 629mergeMode: after => 1, 2, 3, 4, 5, 6 ✔️ 630mergeMode: before => 4, 5, 6, 1, 2, 3 631 632// An example where mergeMode: before works better 633skip: 0, limit: 3 => 4, 5, 6 634skip: 3, limit: 3 => 1, 2, 3 635 636mergeMode: after => 4, 5, 6, 1, 2, 3 637mergeMode: before => 1, 2, 3, 4, 5, 6 ✔️ 638``` 639 640### Relay Pagination 641 642Given we have a [relay-compatible schema](https://facebook.github.io/relay/graphql/connections.htm) 643on our backend, we can offer the possibility of endless data resolving. 644This means that when we fetch the next page in our data 645received in `useQuery` we'll see the previous pages as well. This is useful for 646endless scrolling. 647 648We can achieve this by importing `relayPagination` from `@urql/exchange-graphcache/extras`. 649 650```js 651import { cacheExchange } from '@urql/exchange-graphcache'; 652import { relayPagination } from '@urql/exchange-graphcache/extras'; 653 654const cache = cacheExchange({ 655 resolvers: { 656 Query: { 657 todos: relayPagination(), 658 }, 659 // Or if the pagination happens in a nested field: 660 User: { 661 todos: relayPagination(), 662 }, 663 }, 664}); 665``` 666 667`relayPagination` accepts an object of options, for now we are offering one 668option and that is the `mergeMode`. This defaults to `inwards` and can otherwise 669be set to `outwards`. This will handle how pages are merged when we paginate 670forwards and backwards at the same time. outwards pagination assumes that pages 671that come in last should be merged before the first pages, so that the list 672grows outwards in both directions. The default inwards pagination assumes that 673pagination last pages is part of the same list and come after first pages. 674Hence it merges pages so that they converge in the middle. 675 676Example series of requests: 677 678``` 679first: 1 => node 1, endCursor: a 680first: 1, after: a => node 2, endCursor: b 681... 682last: 1 => node 99, startCursor: c 683last: 1, before: c => node 89, startCursor: d 684``` 685 686With inwards merging the nodes will be in this order: `[1, 2, ..., 89, 99]` 687And with outwards merging: `[..., 89, 99, 1, 2, ...]` 688 689The helper happily supports schema that return nodes rather than 690individually-cursored edges. For each paginated type, we must either 691always request nodes, or always request edges -- otherwise the lists 692cannot be stiched together. 693 694### Reading on 695 696[On the next page we'll learn about "Cache Directives".](./local-directives.md)