17

I have a Gatsby project with very similar GraphQL queries for two different types of content: regular pages and wiki articles.

Page by slug

export const query = graphql`
  query($slug: String!) {
    page: contentfulPage(slug: {eq: $slug}) {
      title
      slug
      body {
        remark: childMarkdownRemark {
          excerpt
          html
          headings {
            value
            depth
          }
        }
      }
      updatedAt(formatString: "D. MMM YYYY")
      authors {
        name
        email
      }
    }
  }
`

Wiki article by slug

export const query = graphql`
  query($slug: String!) {
    article: contentfulWikiArticle(slug: {eq: $slug}) {
      title
      slug
      body {
        remark: childMarkdownRemark {
          excerpt
          html
          headings {
            value
            depth
          }
        }
      }
      updatedAt(formatString: "D. MMM YYYY")
      authors {
        name
        email
      }
 +    section {
 +      title
 +      slug
 +    }
 +    subsection {
 +      title
 +      slug
 +    }
    }
  }
`

Except for the additional section and subsection for wiki articles, the queries are identical. To keep things DRY, how can I move the page fields into a separate fragment that can also be spread into the wiki article query despite being of different type? Could GraphQL provide something like:

fragment pageFields on [ContenfulPage, ContenfulWikiArticle] {
  ...
}
Janosh
  • 3,392
  • 2
  • 27
  • 35
  • 1
    Great question I have a similar question ? Did you figure this out. – me-me Sep 05 '18 at 16:48
  • 1
    @me-me Not yet, I’m afraid. – Janosh Sep 06 '18 at 06:26
  • Not sure if you resolved, It's hard to explain but If I saw the source I would be able to answer in a well formatted way but consider making a "TemplateWrapper Component" From layout.js in the components folder and making a "LayoutfFagment" for Contentful fields you are building in components then declared shared data or fields that is repeated null then on export the query and filter the shared data or fields. there is a few more steps after this like I side reach out if you are still stuck. – Nick Cappello Feb 08 '19 at 16:10
  • @NickC Thanks for your comment but what I'm really asking is if GraphQL provides a native way to do this. I don't quite understand what you're suggesting but it sounds a little hacky. – Janosh Feb 08 '19 at 16:16
  • Using Apollo would "natively" accomplish this – Nick Cappello Feb 11 '19 at 16:39

1 Answers1

12

Gatsby recent release allow users to set up their own types for graphql schema, making this question finally possible.

It's always been possible with graphql if users have control of the schema, but thanks to the recent Gatsby update, users can finally implement this on their own.

Setup

To set up a simple example, I'll use gatsby-transformer-json on a simple folder like this

jsonFolder
  |--one.json { "type": "One", "name": "a", "food": "pizza" }
  `--two.json { "type": "Two", "name": "b", "game": "chess" }

and use the option to declare my type name:

{
  resolve: `gatsby-transformer-json`,
  options: { 
    typeName: ({ object }) => object.type,
  },
},

Now I have two types that was created for me. I can create a fragment on one of them, but not both:

export const name = graphql`
  fragment name on One {
    name
  }
`

export const pageQuery = graphql`
  query {
    one {
      ...name
    }
    two {
      ...name <-- ⚠️ throw type error
    }
  }
`

Let's fix this.

Setting up types

I'll use a new API called createTypes to register a new interface & the 2 types for each of the json. Note that JsonNode contains common fields of both One and Two:

exports.sourceNodes = ({ actions }) => {
  const { createTypes } = actions
  const typeDefs = `
    interface JsonNode {
      name: String
      type: String!
    }

    type One implements Node & JsonNode {
      name: String
      type: String!
      food: String
    }

    type Two implements Node & JsonNode {
      name: String
      type: String!
      game: String
    }
  `
  createTypes(typeDefs)
}

The magic happens on this line, where One & Two implements both JsonNode (custom interface) and Node (Gatsby's interface).

type One implements Node & JsonNode { ... }

Now I can write a fragment that implements JsonNode & it'll work for both types.

// blogPostTemplate.js
import React from "react"
import { graphql } from "gatsby"

export default ({ data }) => <div>{JSON.Stringify(data)}</div>

export const name = graphql`
  fragment name on JsonNode {
    name
    level
  }
`

export const pageQuery = graphql`
  query {
    one {
      ...name <-  works
    }
    two {
      ...name <-  works
    }
  }
`

This requires a bit of setup, but might be worth it if you know your data type in advance & need to reuse fragments a lot.

Derek Nguyen
  • 11,294
  • 1
  • 40
  • 64