Gatsby: GraphQL fragments and repetition in queries

Created on 26 Feb 2019  路  8Comments  路  Source: gatsbyjs/gatsby

Summary

How can I extract repeating parts of GraphQL queries?

Motivation

I have two GraphQL queries which are ~50% identical. My website will exhibit inconsistent behavior if I will accidentally modify one query and forget to change the other. These queries are also fairly verbose. For these reasons I would like to extract the repeating portion of these queries to its own file.

What I've tried so far

  1. I looked into GraphQL query fragments. I found these query fragments used by Gatsby. I noticed they don't have nesting, and they are specifying where exactly the fragment can be used. I tried to google how to do nested fragments, but couldn't figure it out.

  2. Next I tried String interpolation. I would import a String which has the repeated portion. This worked for my query inside gatsby-node.js, but it didn't work for my query inside a template file. I don't really understand why. I know that I can't do String interpolation in StaticQueries, but this is not a StaticQuery, it's a normal one, so I was expecting String interpolation to work.

  3. Since query variables with $name syntax were working, I tried to put the entire repeating portion in a query variable and just drop it in the query like a normal query variable. This didn't work either.

awaiting author response question or discussion

Most helpful comment

You can include the whole part from the root as a fragment too if you want, eg

fragment MarkdownQueryFragment on Query {
  allMarkdownRemark(... all your args ...) {
    ...
  }
}

Other options is to reuse filter fields as variables, because you can pass them to pages when you create them. Then you can pass variable sort and reuse it as just javascript object. After that you can reuse the selection set of the rest of the query as a fragment.

Ultimately it depends on what you are trying to achieve, could you link me to the queries that you have repeated? I can offer a more directed advice in that case.

All 8 comments

You can extract repeated parts into query fragments and collocate them with components. Check out this guide.

I think this part is my problem when I'm trying to create a fragment: sort: { fields: [fields___prefix, fields___slug] order: DESC }. I guess fragments can't be made for that purpose? Or what would be the TypeName for that fragment?

You can include the whole part from the root as a fragment too if you want, eg

fragment MarkdownQueryFragment on Query {
  allMarkdownRemark(... all your args ...) {
    ...
  }
}

Other options is to reuse filter fields as variables, because you can pass them to pages when you create them. Then you can pass variable sort and reuse it as just javascript object. After that you can reuse the selection set of the rest of the query as a fragment.

Ultimately it depends on what you are trying to achieve, could you link me to the queries that you have repeated? I can offer a more directed advice in that case.

It seems I'm unable to get custom fragments working at all. This is the most recent error I'm seeing:

  Error: It appears like Gatsby is misconfigured. Gatsby related `graphql` calls are supposed to only be evaluated at compile time, and then compiled away,. Unfortunately, something went wrong and the query was   left in the compiled code.
  .Unless your site has a complex or custom babel/Gatsby configuration this is likely a bug in Gatsby.

Here's what I did before seeing that error:

First I tried to import the fragment like the example in the docs you linked. This didn't work, because my gatsby-node.js has several lines of this syntax:

const fs = require('fs');

and apparently I can't mix that syntax with this syntax:

import blogPostTeaserFields from "./fragments"

So I tried to refactor fragments.js like this:

const {聽graphql } = require("gatsby");

const blogPostTeaserFields = graphql`
    fragment BlogPostTeaserFields on Edges {
        node {
            id
            excerpt
            fields {
                slug
                prefix
                source
            }
            frontmatter {
                title
                tags
                cover {
                    children {
                        ... on ImageSharp {
                            fluid(maxWidth: 800, maxHeight: 360, cropFocus: CENTER, quality: 90, traceSVG: { color: "#f9ebd2" }) {
                                tracedSVG
                                aspectRatio
                                src
                                srcSet
                                srcWebp
                                srcSetWebp
                                sizes
                            }
                        }
                    }
                }
            }
        }
    }
`

module.exports = {
    blogPostTeaserFields: blogPostTeaserFields
};

And then I tried to import it in gatsby-node like this:

const { blogPostTeaserFields } = require(`./src/fragments.js`);

And now I'm seeing this error:

  Error: It appears like Gatsby is misconfigured. Gatsby related `graphql` calls are supposed to only be evaluated at compile time, and then compiled away,. Unfortunately, something went wrong and the query was   left in the compiled code.
  .Unless your site has a complex or custom babel/Gatsby configuration this is likely a bug in Gatsby.

Ultimately it depends on what you are trying to achieve, could you link me to the queries that you have repeated? I can offer a more directed advice in that case.

I've implemented infinite scroll with fallback to pagination. I'm generating the paginated pages with createPage calls inside gatsby-node. In order to make infinite scroll work efficiently, it needs access to JSON corresponding to those individual pages. However, Gatsby doesn't provide me access to that JSON, so I have to recreate it separately inside gatsby-node. So in summary, I'm taking some input data and I'm outputting both "normal pages" and "custom JSON". I want those to correspond to each other consistently. They do right now, and to keep it that way, I would like to extract the duplicated parts of the corresponding queries somewhere (so that I can't accidentally change one query and forget to change the other).

This is the query in gatsby-node.js:

{
            allMarkdownRemark(
              ` + filters + `
              ` + blogPostSort + `
              limit: 1000
            ) {
              ` + blogPostTeaserFields + `
            }
          }

String interpolation works really nicely there.

let filters = `filter: { fields: { slug: { ne: null } } }`;
if (activeEnv == "production") filters = `filter: { fields: { slug: { ne: null } , prefix: { ne: null } } }`

const blogPostTeaserFields = `
    edges {
        node {
            id
            excerpt
            fields {
                slug
                prefix
                source
            }
            frontmatter {
                title
                tags
                cover {
                    children {
                        ... on ImageSharp {
                            fluid(maxWidth: 800, maxHeight: 360, cropFocus: CENTER, quality: 90, traceSVG: { color: "#f9ebd2" }) {
                                tracedSVG
                                aspectRatio
                                src
                                srcSet
                                srcWebp
                                srcSetWebp
                                sizes
                            }
                        }
                    }
                }
            }
        }
    }
`

const blogPostSort = `
    sort: { fields: [fields___prefix, fields___slug] order: DESC }
`

So the query in gatsby-node.js is used to generate the JSON. Compare that to the query in index template, which is used to generate the "normal pages":

export const query = graphql`
  query IndexQuery($skip: Int!, $limit: Int!, $filePathRegex: String!) {
    posts: allMarkdownRemark(
      filter: {
        fileAbsolutePath: { regex: $filePathRegex }
      }
      sort: { fields: [fields___prefix, fields___slug], order: DESC }
      limit: $limit
      skip: $skip
    ) {
      edges {
        node {
          excerpt
          fields {
            slug
            prefix
          }
          frontmatter {
            title
            tags
            cover {
              children {
                ... on ImageSharp {
                  fluid(maxWidth: 800, maxHeight: 360, cropFocus: CENTER, quality: 90, traceSVG: { color: "#f9ebd2" }) {
                    tracedSVG
                    aspectRatio
                    src
                    srcSet
                    srcWebp
                    srcSetWebp
                    sizes
                  }
                }
              }
            }
          }
        }
      }
    }
  }
`;

So there's 2 parts I would like to extract: blogPostSort and blogPostTeaserFields.

I solved my issue in a different way (I decided to pass relevant portion of results from the first GraphQL query as pageContext, instead of making a second GraphQL query).

I'm still interested in learning why I couldn't get fragments working, so I opened a new issue about it. I'm closing this one because I've bloated the issue so much that I don't think anyone will want to read through this.

@baobabKoodaa Are you able to share a snippet of your solution?

@baobabKoodaa Are you able to share a snippet of your solution?

Full solution here: https://github.com/baobabKoodaa/blog/

This is where I make the (only) query: https://github.com/baobabKoodaa/blog/blob/e3a9a830e649ca6a6a0ff72e4032780341d5bf23/gatsby-node.js#L69

This is where I pass the (relevant portion of) results of that query as pageContext instead of making a second query for the same information: https://github.com/baobabKoodaa/blog/blob/e3a9a830e649ca6a6a0ff72e4032780341d5bf23/gatsby-node.js#L180

This is where I use the pageContext parameters:
https://github.com/baobabKoodaa/blog/blob/e3a9a830e649ca6a6a0ff72e4032780341d5bf23/src/templates/index.js#L22

Was this page helpful?
0 / 5 - 0 ratings

Related issues

3CordGuy picture 3CordGuy  路  3Comments

jimfilippou picture jimfilippou  路  3Comments

Oppenheimer1 picture Oppenheimer1  路  3Comments

andykais picture andykais  路  3Comments

ghost picture ghost  路  3Comments