Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.

title: Local Directives order: 3#

Local Directives#

Previously, we've learned about local resolvers on the "Normalized Caching" page and the "Local Resolvers" page.

Resolvers allow us to change the data that Graphcache resolvers for a given field on a given type. This, in turn, allows us to change which links and data are returned in a query’s result, which otherwise may not be cached or be returned in a different shape.

Resolvers are useful to globally change how a field behaves, for instance, to tell Graphcache that a Query.item(id: $id) field returns an item of type Item with the id field, or to transform a value before it’s used in the UI.

However, resolvers are limited to changing the behaviour globally, not to change a field’s behaviour per query. This is why local directives exist.

Adding client-only directives#

Any directive in our GraphQL documents that’s prefixed with an underscore character (_) will be filtered by @urql/core. This means that our GraphQL API never sees it and it becomes a “client-only directive”.

No matter whether we prefix a directive or not however, we can define local resolvers for directives in Graphcache’s configuration and make conditional local resolvers.

cacheExchange({
  directives: {
    pagination(directiveArgs) {
      // This resolver is called for @_pagination directives
      return (parent, args, cache, info) => {
        return null;
      };
    },
  },
});

Once we define a directive on the directives configuration object, we can reference it in our GraphQL queries.

As per the above example, if we now reference @_pagination in a query, the resolver that’s returned in the configuration will be applied to the field, just like a local resolver.

We can also reference the directive using @pagination, however, this will mean that it’s also sent to the API, so this usually isn’t what we want.

Client-controlled Nullability#

Graphcache comes with two directives built-in by default. The optional and required directives. These directives can be used as an alternative to the Schema Awareness feature’s ability to generate partial results.

If we were to write a query that contains @_optional on a field, then the field is always allowed to be nullable. In case it’s not cached, Graphcache will be able to replace it with a null value.

Similarly, if we annotate a field with @_required, the value is not optional and, even if the cache knows the value is set to null, it will become required and Graphcache will either cascade to the next higher parent field annotated with @_optional, or will mark a query as a cache-miss.

Pagination#

Previously, in the “Local Resolvers” page’s Pagination section we defined a local resolver to add infinite pagination to a given type’s field.

If we add the simplePagination or relayPagination helpers as directives instead, we can still use our schema’s pagination field as normal, and instead, only use infinite pagination as required.

import { simplePagination } from '@urql/exchange-graphcache/extras';
import { relayPagination } from '@urql/exchange-graphcache/extras';

cacheExchange({
  directives: {
    simplePagination: options => simplePagination({ ...options }),
    relayPagination: options => relayPagination({ ...options }),
  },
});

Defining directives for our resolver factory functions means that we can now use them selectively.

{
  todos(first: 10) @_relayPagination(mergeMode: "outwards") {
    id
    text
  }
}

Reading on#

On the next page we'll learn about "Cache Updates".