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:
query myQuery($someTest: Boolean) {experimentalField @skip(if: $someTest)}
This example shows the @skip
directive, which is a built-in directive (i.e., it's part of the
- 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
@client
The @client
directive allows you to resolve client-only data alongside your server data. These fields are not sent to the GraphQL server.
query LaunchDetails($launchId: ID!) {launch(id: $launchId) {siterocket {type# resolved locally on the client,# removed from the request to the serverdescription @client}}}
For more information about the @client
directive, see this section on @client
directive is also useful for
@connection
@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
query Feed($offset: Int, $limit: Int) {feed(offset: $offset, limit: $limit) @connection(key: "feed") {...FeedFields}}
@defer
@defer
Beginning with version 3.7.0
, Apollo Client provides preview support for @defer
directive
To use the @defer
directive, we apply it to an inline or named fragment that contains all slow-resolving fields:
query PersonQuery($personId: ID!) {person(id: $personId) {# Basic fields (fast)idfirstNamelastName# Friend fields (slower)... @defer {friends {id}}}}
Note: in order to use @defer
in a React Native application, additional configuration is required. See the
For more information about the @defer
directive, check out the @defer
docs
@export
@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:
const GET_CURRENT_AUTHOR_POST_COUNT = gql`query CurrentAuthorPostCount($authorId: Int!) {currentAuthorId @client @export(as: "authorId")postCount(authorId: $authorId)}`;
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
@nonreactive
Since 3.8.0
@nonreactive
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:
const TrailFragment = gql`fragment TrailFragment on Trail {namestatus}`;const ALL_TRAILS = gql`query allTrails {allTrails {id...TrailFragment @nonreactive}}${TrailFragment}`;function App() {const { data, loading } = useQuery(ALL_TRAILS);return (<main><h2>Ski Trails</h2><ul>{data?.trails.map((trail) => (<Trail key={trail.id} id={trail.id} />))}</ul></main>);}
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"
:
const Trail = ({ id }) => {const [updateTrail] = useMutation(UPDATE_TRAIL);const { data } = useFragment({fragment: TrailFragment,from: {__typename: "Trail",id,},});return (<li key={id}>{data.name} - {data.status}<inputchecked={data.status === "OPEN" ? true : false}type="checkbox"onChange={(e) => {updateTrail({variables: {trailId: id,status: e.target.checked ? "OPEN" : "CLOSED",},});}}/></li>);};
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.