graphql fragments: reuse and variables

2020-01-09

 | 

~5 min read

 | 

812 words

After thinking more about Lee Byron’s talk and some of the uses for fragments1, I still had some questions. Specifically - how do you reuse them effectively?

It’s one thing to break down complicated queries into smaller fragments (as Lee did in his talk), but it’s another to actually reuse those query fragments across the app.

While researching, I also found the use of variables with fragments simple and elegant.

I’ll look at both below.

Reusing Fragments

At the end of the day, GraphQL fragments are template literals assigned to a variable. In Javascript, that means they can be passed around just like any other variable. Depending on the environment you’re in, the convention may be slightly different.

For example, the Apollo convention is to place fragments within an object named .fragment:

CommentsFragment.js
import gql from "graphql-tag"

CommentsPage.fragments = {
  comment: gql`
    fragment CommentsPageComment on Comment {
      id
      postedBy {
        login
        html_url
      }
      createdAt
      content
    }
  `,
}

Then, to use this, would look like:

UsingFragments.js
const SUBMIT_COMMENT_MUTATION = gql`
  mutation SubmitComment($repoFullName: String!, $commentContent: String!) {
    submitComment(
      repoFullName: $repoFullName
      commentContent: $commentContent
    ) {
      ...CommentsPageComment
    }
  }
  ${CommentsPage.fragments.comment}
`

export const COMMENT_QUERY = gql`
  query Comment($repoName: String!) {
    # ...
    entry(repoFullName: $repoName) {
      # ...
      comments {
        ...CommentsPageComment
      }
      # ...
    }
  }
  ${CommentsPage.fragments.comment}
`

The interesting thing to note about Apollo’s implementation is the use of the ...Name for the fragment and the inclusion of the gql template literal (${CommentsPage.fragments.comment}).

Gatsby’s approach is somewhat different and is a result of the architecture of a Gatsby site which allows defining GraphQL fragments in components, but only returns data to queries within Pages.

In the example from the Gatsby docs then, we:

  1. Create a component with a GraphQL fragment (the fragment is exported and available as SiteInformation)
  2. Import the component into a Page and use the fragment

The example (taken from the Gatsby site linked above):

src/components/IndexPost.jsx
import React from "react"
import { graphql } from "gatsby"

export default (props) => {
  return /*...*/
}

export const query = graphql`
  fragment SiteInformation on Site {
    siteMetadata {
      title
      siteDescription
    }
  }
`

And then used like so:

src/pages/main.jsx
import React from "react"
import { graphql } from "gatsby"
import IndexPost from "../components/IndexPost"

export default ({ data }) => {
  return (
    <div>
      <h1>{data.site.siteMetadata.title}</h1>
      <p>{data.site.siteMetadata.siteDescription}</p>

      {/*
        Or you can pass all the data from the fragment
        back to the component that defined it
      */}
      <IndexPost siteInformation={data.site.siteMetadata} />
    </div>
  )
}

export const query = graphql`
  query {
    site {
      ...SiteInformation
    }
  }
`

As of now, my questions that remain to be tested include:

  1. Why does Apollo require the ${FragmentName}?

  2. Can I export and use a fragment that’s defined as:

    siteInfoFragment.js
    export const siteInfoFragment = graphql`
      fragment SiteNformation on Site {
        siteMetadata {
          title
          siteDescription
        }
      }
    `

Right now, I’m still learning the theory of fragments and… I like them in theory. However, I need more experience with them and to date, my apps which use GraphQL simply haven’t called for the complexity. So, this is preliminary research and I’m excited to revisit when there’s an actual need.

Using Variables With Fragments

While researching how to pass fragments around, I also found the answer to a question I didn’t realize I had: how to use variables with fragments.

I found the answer in the GraphQL docs, which provide a nice example of how to use variables with fragments. Before looking at their implementation, let’s examine a more generic query to see how it compares:

HeroComparison.gql
query HeroComparison($first: Int = 3) {
  firstComparison: hero(episode: EMPIRE) {
    name
    friendsConnection(first: $first) {
      totalCount
      edges {
        node {
          name
        }
      }
    }
  }
  secondComparison: hero(episode: JEDI) {
    name
    friendsConnection(first: $first) {
      totalCount
      edges {
        node {
          name
        }
      }
    }
  }
}

In this example, the client would provide a value for the variable first (which defaults to 3), and that is then passed along to both the firstComparison and secondComparison.

Using Fragments, we can simplify this and reduce the duplicative code while still using variables as we would expect. Here’s what the GraphQL docs suggest:

HeroComparisonWithFragments.gql
query HeroComparison($first: Int = 3) {
  leftComparison: hero(episode: EMPIRE) {
    ...comparisonFields
  }
  rightComparison: hero(episode: JEDI) {
    ...comparisonFields
  }
}

fragment comparisonFields on Character {
  name
  friendsConnection(first: $first) {
    totalCount
    edges {
      node {
        name
      }
    }
  }
}

The key point is that, just as in the original query, the variable passed into the query is propagated all the way down and is accessible by the fragments.

Conclusion

When it comes to fragments, it turns out that the way to pass them around and reuse them or to use variables is exactly how I’d expect to do it. That’s pretty fantastic.

Footnotes



Hi there and thanks for reading! My name's Stephen. I live in Chicago with my wife, Kate, and dog, Finn. Want more? See about and get in touch!