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:
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.
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.
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:
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:
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.
@nonreactive
Since 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:
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"
:
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.
@unmask
Since 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.
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}
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
.
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.
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:
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.