From ea652a0884ab6cadde3f941da1725219fef8f6c6 Mon Sep 17 00:00:00 2001 From: Guillaume Chau Date: Sun, 1 Dec 2019 19:01:22 +0100 Subject: [PATCH] docs: fragments --- packages/docs/src/.vuepress/config.js | 1 + .../docs/src/guide-composable/fragments.md | 307 ++++++++++++++++++ 2 files changed, 308 insertions(+) create mode 100644 packages/docs/src/guide-composable/fragments.md diff --git a/packages/docs/src/.vuepress/config.js b/packages/docs/src/.vuepress/config.js index 4afc53e..85bc303 100644 --- a/packages/docs/src/.vuepress/config.js +++ b/packages/docs/src/.vuepress/config.js @@ -131,6 +131,7 @@ module.exports = { 'mutation', 'subscription', 'pagination', + 'fragments', ], }, ], diff --git a/packages/docs/src/guide-composable/fragments.md b/packages/docs/src/guide-composable/fragments.md new file mode 100644 index 0000000..c4b122e --- /dev/null +++ b/packages/docs/src/guide-composable/fragments.md @@ -0,0 +1,307 @@ +# Using fragments + +A [GraphQL fragment](http://graphql.org/learn/queries/#fragments) is a shared piece of query logic. + +```graphql +fragment NameParts on Person { + firstName + lastName +} + +query GetPerson { + people(id: "7") { + ...NameParts + avatar(size: LARGE) + } +} +``` + +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`](https://github.com/apollographql/apollo-client/tree/master/packages/graphql-anywhere) and [`graphql-tag`](https://github.com/apollographql/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: + +```js +import gql from 'graphql-tag' + +export const commentFragment = { + comment: gql` + fragment CommentsPageComment on Comment { + id + postedBy { + login + htmlUrl + } + createdAt + content + } + `, +} +``` + +This can be done either in our component ` + + +``` + +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](https://github.com/apollographql/graphql-tag/blob/master/loader.js), we can include fragments using `import` statements. For example: + +```graphql +#import "./someFragment.graphql" + +query getSomething { + something { + ...SomethingFragment + } +} +``` + +Will make the contents of `someFragment.graphql` available to the current file. See the [Webpack Fragments](/integrations/webpack/#fragments) section for additional details. + +## Fragments on unions and interfaces + +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: + +```graphql +query { + allPeople { + ... on Character { + name + } + ... on Jedi { + side + } + ... on Droid { + model + } + } +} +``` + +In the query above, `allPeople` 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: + +1. 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](https://graphql-code-generator.com) that does it. Define where your GraphQL Schema is available and where to write the file: + +```yaml +# codegen.yml +schema: YOUR_API +overwrite: true +generates: + ./fragmentTypes.json: + plugins: + - fragment-matcher +``` + +With all of that, you simply run: + +```shell +gql-gen +``` + +> To learn more, you can read the ["Fragment Matcher" chapter](https://graphql-code-generator.com/docs/plugins/fragment-matcher). + +In order to introspect the server manually, set this as a script to run at build time. + +```js +const fetch = require('node-fetch') +const fs = require('fs') + +fetch(`${YOUR_API_HOST}/graphql`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + variables: {}, + query: ` + { + __schema { + types { + kind + name + possibleTypes { + name + } + } + } + } + `, + }), +}) + .then(result => result.json()) + .then(result => { + // here we're filtering out any type information unrelated to unions or interfaces + const filteredData = result.data.__schema.types.filter( + type => type.possibleTypes !== null, + ) + result.data.__schema.types = filteredData + fs.writeFile('./fragmentTypes.json', JSON.stringify(result.data), err => { + if (err) { + console.error('Error writing fragmentTypes file', err) + } else { + console.log('Fragment types successfully extracted!') + } + }) + }) +``` + +2. 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. + +```js +import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory' +import introspectionQueryResultData from './fragmentTypes.json' + +const fragmentMatcher = new IntrospectionFragmentMatcher({ + introspectionQueryResultData +}) +``` + +3. Pass in the newly created `IntrospectionFragmentMatcher` to configure your cache during construction. Then, you pass your newly configured cache to `ApolloClient` to complete the process. + +```js +import ApolloClient from 'apollo-client' +import { InMemoryCache } from 'apollo-cache-inmemory' +import { HttpLink } from 'apollo-link-http' + +// add fragmentMatcher code from step 2 here + +const cache = new InMemoryCache({ fragmentMatcher }) + +const client = new ApolloClient({ + cache, + link: new HttpLink(), +}) +```