Using fragments
Learn how to use fragments to share fields across queries
A GraphQL fragment is a shared piece of query logic.
1fragment NameParts on Person {
2 firstName
3 lastName
4}
5
6query GetPerson {
7 people(id: "7") {
8 ...NameParts
9 avatar(size: LARGE)
10 }
11}
It's important to note that the component after the on
clause is designated for the type we are selecting from. In this case, people
is of type Person
and we want to select the firstName
and lastName
fields from people(id: "7")
.
There are two principal uses for fragments in Apollo:
Sharing fields between multiple queries, mutations or subscriptions.
Breaking your queries up to allow you to co-locate field access with the places they are used.
In this document we'll outline patterns to do both; we'll also make use of utilities in the graphql-anywhere
and graphql-tag
packages which aim to help us, especially with the second problem.
Reusing fragments
The most straightforward use of fragments is to reuse parts of queries (or mutations or subscriptions) in various parts of your application. For instance, in GitHunt on the comments page, we want to fetch the same fields after posting a comment as we originally query. This way we can be sure that we render consistent comment objects as the data changes.
To do so, we can simply share a fragment describing the fields we need for a comment:
1import gql from 'graphql-tag';
2
3CommentsPage.fragments = {
4 comment: gql`
5 fragment CommentsPageComment on Comment {
6 id
7 postedBy {
8 login
9 html_url
10 }
11 createdAt
12 content
13 }
14 `,
15};
We put the fragment on CommentsPage.fragments.comment
by convention, and use the familiar gql
helper to create it.
When it's time to embed the fragment in a query, we simply use the ...Name
syntax in our GraphQL, and embed the fragment inside our query GraphQL document:
1const SUBMIT_COMMENT_MUTATION = gql`
2 mutation SubmitComment($repoFullName: String!, $commentContent: String!) {
3 submitComment(repoFullName: $repoFullName, commentContent: $commentContent) {
4 ...CommentsPageComment
5 }
6 }
7 ${CommentsPage.fragments.comment}
8`;
9
10export const COMMENT_QUERY = gql`
11 query Comment($repoName: String!) {
12 # ...
13 entry(repoFullName: $repoName) {
14 # ...
15 comments {
16 ...CommentsPageComment
17 }
18 # ...
19 }
20 }
21 ${CommentsPage.fragments.comment}
22`;
You can see the full source code to the CommentsPage
in GitHunt here.
Colocating fragments
A key advantage of GraphQL is the tree-like nature of the response data, which in many cases mirrors your rendered component hierarchy. This, combined with GraphQL's support for fragments, allows you to split your queries up in such a way that the various fields fetched by the queries are located right alongside the code that uses the field.
Although this technique doesn't always make sense (for instance it's not always the case that the GraphQL schema is driven by the UI requirements), when it does, it's possible to use some patterns in Apollo client to take full advantage of it.
In GitHunt, we show an example of this on the FeedPage
, which constructs the following view hierarchy:
1FeedPage
2└── Feed
3 └── FeedEntry
4 ├── RepoInfo
5 └── VoteButtons
The FeedPage
conducts a query to fetch a list of Entry
s, and each of the subcomponents requires different subfields of each Entry
.
The graphql-anywhere
package gives us tools to easily construct a single query that provides all the fields that each subcomponent needs, and allows to easily pass the exact field that a component needs to it.
Creating fragments
To create the fragments, we again use the gql
helper and attach to subfields of ComponentClass.fragments
, for example:
1VoteButtons.fragments = {
2 entry: gql`
3 fragment VoteButtons on Entry {
4 score
5 vote {
6 vote_value
7 }
8 }
9 `,
10};
If our fragments include sub-fragments then we can pass them into the gql
helper:
1FeedEntry.fragments = {
2 entry: gql`
3 fragment FeedEntry on Entry {
4 commentCount
5 repository {
6 full_name
7 html_url
8 owner {
9 avatar_url
10 }
11 }
12 ...VoteButtons
13 ...RepoInfo
14 }
15 ${VoteButtons.fragments.entry}
16 ${RepoInfo.fragments.entry}
17 `,
18};
Filtering with fragments
We can also use the graphql-anywhere
package to filter the exact fields from the entry
before passing them to the subcomponent. So when we render a VoteButtons
, we can simply do:
1import { filter } from 'graphql-anywhere';
2
3<VoteButtons
4 entry={filter(VoteButtons.fragments.entry, entry)}
5 canVote={loggedIn}
6 onVote={type => onVote({
7 repoFullName: full_name,
8 type,
9 })}
10/>
The filter()
function will grab exactly the fields from the entry
that the fragment defines.
Importing fragments when using Webpack
When loading .graphql
files with graphql-tag/loader, we can include fragments using import
statements. For example:
1#import "./someFragment.graphql"
Will make the contents of someFragment.graphql
available to the current file. See the Webpack Fragments section for additional details.
Fragments on unions and interfaces
This is an advanced feature that Apollo Boost does not support. Learn how to set Apollo Client up manually in our Apollo Boost migration guide.
By default, Apollo Client doesn't require any knowledge of the GraphQL schema, which means it's very easy to set up and works with any server and supports even the largest schemas. However, as your usage of Apollo and GraphQL becomes more sophisticated, you may start using fragments on interfaces or unions. Here's an example of a query that uses fragments on an interface:
1query {
2 all_people {
3 ... on Character {
4 name
5 }
6 ... on Jedi {
7 side
8 }
9 ... on Droid {
10 model
11 }
12 }
13}
In the query above, all_people
returns a result of type Character[]
. Both Jedi
and Droid
are possible concrete types of Character
, but on the client there is no way to know that without having some information about the schema. By default, Apollo Client's cache will use a heuristic fragment matcher, which assumes that a fragment matched if the result included all the fields in its selection set, and didn't match when any field was missing. This works in most cases, but it also means that Apollo Client cannot check the server response for you, and it cannot tell you when you're manually writing invalid data into the store using update
, updateQuery
, writeQuery
, etc. Also, the heuristic fragment matcher will not work accurately when using fragments with unions or interfaces. Apollo Client will let you know this with a console warning (in development), if it attempts to use the default heuristic fragment matcher with unions/interfaces. The IntrospectionFragmentMatcher
is the solution for working with unions/interfaces, and is explained in more detail below.
The section below explains how to pass the necessary schema knowledge to the Apollo Client cache so unions and interfaces can be accurately matched and results validated before writing them into the store.
To support result validation and accurate fragment matching on unions and interfaces, a special fragment matcher called the IntrospectionFragmentMatcher
can be used. If there are any changes related to union or interface types in your schema, you will have to update the fragment matcher accordingly.
We recommend setting up a build step that extracts the necessary information from the schema into a JSON file, where it can be imported from when constructing the fragment matcher. To set it up, follow the three steps below:
Query your server / schema to obtain the necessary information about unions and interfaces and write it to a file.
You can automate this or set this as a script to run at build time.
If you want to auto-generate the introspection result, there's a tool called GraphQL Code Generator that does it. Define where your GraphQL Schema is available and where to write the file:
1# codegen.yml
2schema: YOUR_API
3overwrite: true
4generates:
5 ./fragmentTypes.json:
6 plugins:
7 - fragment-matcher
With all of that, you simply run:
1gql-gen
To learn more, you can read the "Fragment Matcher" chapter.
In order to introspect the server manually, set this as a script to run at build time.
1const fetch = require('node-fetch');
2const fs = require('fs');
3
4fetch(`${YOUR_API_HOST}/graphql`, {
5 method: 'POST',
6 headers: { 'Content-Type': 'application/json' },
7 body: JSON.stringify({
8 variables: {},
9 query: `
10 {
11 __schema {
12 types {
13 kind
14 name
15 possibleTypes {
16 name
17 }
18 }
19 }
20 }
21 `,
22 }),
23})
24 .then(result => result.json())
25 .then(result => {
26 // here we're filtering out any type information unrelated to unions or interfaces
27 const filteredData = result.data.__schema.types.filter(
28 type => type.possibleTypes !== null,
29 );
30 result.data.__schema.types = filteredData;
31 fs.writeFile('./fragmentTypes.json', JSON.stringify(result.data), err => {
32 if (err) {
33 console.error('Error writing fragmentTypes file', err);
34 } else {
35 console.log('Fragment types successfully extracted!');
36 }
37 });
38 });
Create a new IntrospectionFragment matcher by passing in the
fragmentTypes.json
file you just created. You'll want to do this in the same file where you initialize the cache for Apollo Client.
1import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
2import introspectionQueryResultData from './fragmentTypes.json';
3
4const fragmentMatcher = new IntrospectionFragmentMatcher({
5 introspectionQueryResultData
6});
Pass in the newly created
IntrospectionFragmentMatcher
to configure your cache during construction. Then, you pass your newly configured cache toApolloClient
to complete the process.
1import ApolloClient from 'apollo-client';
2import { InMemoryCache } from 'apollo-cache-inmemory';
3import { HttpLink } from 'apollo-link-http';
4
5// add fragmentMatcher code from step 2 here
6
7const cache = new InMemoryCache({ fragmentMatcher });
8
9const client = new ApolloClient({
10 cache,
11 link: new HttpLink(),
12});