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] {
...
}
A GraphQL fragment is a piece of logic that can be shared between multiple queries and mutations. Every fragment includes a subset of the fields that belong to its associated type. In the above example, the Person type must declare firstName and lastName fields for the NameParts fragment to be valid.
Creating GraphQL fragments We can rewrite our query to use a fragment. Fragments are created with the keyword fragment. We can create a fragment called ownerInfo . While creating fragments we have to let GraphQL know on which field it is created.
GraphQL in Action MEAP V05 Inline fragments are, in a way, similar to anonymous functions that you can use without a name. They are just fragments without names and you can spread them inline where you define them.
The __typename field returns the object type's name as a String (e.g., Book or Author ). GraphQL clients use an object's __typename for many purposes, such as to determine which type was returned by a field that can return multiple types (i.e., a union or interface).
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With