Using the @defer directive in Apollo Client

Receive query response data incrementally


The @defer directive is currently at the General Availability stage in Apollo Client, and is available by installing @apollo/client@latest. If you have feedback on it, please let us know via GitHub issues.

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.

For a query to defer fields successfully, the queried endpoint must also support the @defer directive. Entity-based @defer support is also at the General Availability stage in GraphOS Router and is compatible with all federation-compatible subgraph libraries.

Example

Let's say we're building a social media application that can quickly fetch a user's basic profile information, but retrieving that user's friends takes longer.

GraphQL allows us to declare all the fields our UI requires in a single query, but this also means that our query will be as slow as the field that takes the longest to resolve. The @defer directive allows us to mark parts of the query that are not necessary for our app's initial render which will be resolved once it becomes available.

To achieve this, we apply the @defer directive to an in-line fragment that contains all slow-resolving fields related to friend data:

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}

Using this syntax, if the queried server supports @defer, our client can receive the "Basic fields" in an initial response payload, followed by a supplementary payload containing the "Friend fields".

Let's look at an example in React. Here's we can assume GET_PERSON is the above query, PersonQuery, with a deferred list of friends' ids:

JavaScript
app.jsx
1import { gql, useQuery } from "@apollo/client";
2
3function App() {
4  const { loading, error, data } = useQuery(GET_PERSON, {
5    variables: {
6      id: 1,
7    },
8  });
9
10  if (loading) return "Loading...";
11  if (error) return `Error! ${error.message}`;
12
13  return (
14    <>
15      Welcome, {data.firstName} {data.lastName}!
16      <details>
17        <summary>Friends list</summary>
18        {data.friends ? (
19          <ul>
20            {data.friends.map((id) => (
21              <li>{id}</li>
22            ))}
23          </ul>
24        ) : null}
25      </details>
26    </>
27  );
28}

When our call to the useQuery hook first resolves with an initial payload of data, loading will go from true to false and firstName and lastName will be populated with the values from the server. Our deferred fields will not exist as keys on data yet, so we must add conditional logic that checks for their presence. When subsequent chunks of deferred data arrive, useQuery will re-render (loading remains false between re-renders from deferred multipart responses) and data will include the deferred data as they arrive.

For this reason, @defer can be thought of as a tool to improve initial rendering speeds when some slower data will be displayed below the fold or offscreen. In this case, we're rendering the friends list inside a <details> element which is closed by default, avoiding any layout shift as the friends data arrives.

Using with code generation

If you currently use GraphQL Code Generator for your codegen needs, you'll need to use the Codegen version 4.0.0 or higher. Please refer to the Codegen documentation for more information.

Usage in React Native

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

Feedback

Edit on GitHub

Forums