Using GraphQL directives in Apollo Client

Configure GraphQL fields and fragments


A directive decorates part of a GraphQL schema or operation with additional configuration. Tools like Apollo Client can read a GraphQL document's directives and perform custom logic as appropriate.

Directives are preceded by the @ character, like so:

GraphQL
1query myQuery($someTest: Boolean) {
2  experimentalField @skip(if: $someTest)
3}

This example shows the @skip directive, which is a built-in directive (i.e., it's part of the GraphQL specification). It demonstrates the following about directives:

  • Directives can take arguments of their own (if in this case).

  • Directives appear after the declaration of what they decorate (the experimentalField field in this case).

@client

The @client directive allows you to resolve client-only data alongside your server data. These fields are not sent to the GraphQL server.

GraphQL
1query LaunchDetails($launchId: ID!) {
2  launch(id: $launchId) {
3    site
4    rocket {
5      type
6      # resolved locally on the client,
7      # removed from the request to the server
8      description @client
9    }
10  }
11}

For more information about the @client directive, see this section on local-only fields. The @client directive is also useful for client schema mocking before a given field is supported in the GraphQL API your application is consuming.

@connection

The @connection directive allows you to specify a custom cache key for paginated results. For more information, see this section on the @connection directive.

GraphQL
1query Feed($offset: Int, $limit: Int) {
2  feed(offset: $offset, limit: $limit) @connection(key: "feed") {
3    ...FeedFields
4  }
5}

@defer

Beginning with version 3.7.0, Apollo Client provides preview support for the @defer directive. This directive enables your queries to receive data for specific fields incrementally, instead of receiving all field data at the same time. This is helpful whenever some fields in a query take much longer to resolve than others.

To use the @defer directive, we apply it to an inline or named fragment that contains all slow-resolving fields:

GraphQL
1query PersonQuery($personId: ID!) {
2  person(id: $personId) {
3    # Basic fields (fast)
4    id
5    firstName
6    lastName
7
8    # Friend fields (slower)
9    ... @defer {
10      friends {
11        id
12      }
13    }
14  }
15}

Note: in order to use @defer in a React Native application, additional configuration is required. See the React Native docs for more information.

For more information about the @defer directive, check out the @defer docs.

@export

If your GraphQL query uses variables, the local-only fields of that query can provide the values of those variables.

To do so, you apply the @export(as: "variableName") directive, like so:

JavaScript
1const GET_CURRENT_AUTHOR_POST_COUNT = gql`
2  query CurrentAuthorPostCount($authorId: Int!) {
3    currentAuthorId @client @export(as: "authorId")
4    postCount(authorId: $authorId)
5  }
6`;

In the query above, the result of the local-only field currentAuthorId is used as the value of the $authorId variable that's passed to postCount.

You can do this even if postCount is also a local-only field (i.e., if it's also marked as @client).

For more information and other considerations when using the @export directive, check out the local-only fields docs.

@nonreactiveSince 3.8.0

The @nonreactive directive can be used to mark query fields or fragment spreads and is used to indicate that changes to the data contained within the subtrees marked @nonreactive should not trigger rerendering. This allows parent components to fetch data to be rendered by their children without rerendering themselves when the data corresponding with fields marked as @nonreactive change.

Consider an App component that fetches and renders a list of ski trails:

JavaScript
1const TrailFragment = gql`
2  fragment TrailFragment on Trail {
3    name
4    status
5  }
6`;
7
8const ALL_TRAILS = gql`
9  query allTrails {
10    allTrails {
11      id
12      ...TrailFragment @nonreactive
13    }
14  }
15  ${TrailFragment}
16`;
17
18function App() {
19  const { data, loading } = useQuery(ALL_TRAILS);
20  return (
21    <main>
22      <h2>Ski Trails</h2>
23      <ul>
24        {data?.trails.map((trail) => (
25          <Trail key={trail.id} id={trail.id} />
26        ))}
27      </ul>
28    </main>
29  );
30}

The Trail component renders a trail's name and status and allows the user to execute a mutation to toggle the status of the trail between "OPEN" and "CLOSED":

JavaScript
1const Trail = ({ id }) => {
2  const [updateTrail] = useMutation(UPDATE_TRAIL);
3  const { data } = useFragment({
4    fragment: TrailFragment,
5    from: {
6      __typename: "Trail",
7      id,
8    },
9  });
10  return (
11    <li key={id}>
12      {data.name} - {data.status}
13      <input
14        checked={data.status === "OPEN" ? true : false}
15        type="checkbox"
16        onChange={(e) => {
17          updateTrail({
18            variables: {
19              trailId: id,
20              status: e.target.checked ? "OPEN" : "CLOSED",
21            },
22          });
23        }}
24      />
25    </li>
26  );
27};

Notice that the Trail component isn't receiving the entire trail object via props, only the id which is used along with the fragment document to create a live binding for each trail item in the cache. This allows each Trail component to react to the cache updates for a single trail independently. Updates to a trail's status will not cause the parent App component to rerender since the @nonreactive directive is applied to the TrailFragment spread, a fragment that includes the status field.

@unmaskSince 3.12.0

The @unmask directive is used to make fragment data available when using data masking. It is primarily used to incrementally adopt data masking in an existing application. It is considered an escape hatch for all other cases where working with masked data would otherwise be difficult.

GraphQL
1query GetPosts {
2  posts {
3    id
4    ...PostDetails @unmask
5  }
6}
7
8fragment PostDetails on Post {
9  title
10  publishedAt
11  topComment {
12    id
13    ...CommentFragment @unmask
14  }
15}
16
17fragment CommentFragment on Comment {
18  content
19}
note
When the dataMasking option is omitted or set to false, this directive has no effect.

Migrate mode

@unmask is best used to incrementally adopt data masking in existing applications by providing development-only warnings when accessing would-be masked fields throughout your application. To use @unmask in migrate mode, set the mode argument to migrate.

GraphQL
1query GetPost(id: $id) {
2  post(id: $id) {
3    id
4    ...PostDetails @unmask(mode: "migrate")
5  }
6}
7
8fragment PostDetails on Post {
9  title
10}

Now when you access fields that would otherwise be masked, you will see a warning in the console.

TypeScript
1const { data } = useQuery(GET_POST);
2
3const title = data.post.title;

In this example, accessing title on the post from the query result will result in the following warning:

disablecopy=true
Accessing unmasked field on query 'GetPost' at path 'post.title'. This field will not be available when masking is enabled. Please read the field from the fragment instead.

For more information on data masking, see the data masking docs.

Feedback

Edit on GitHub

Forums