Ashbury
Ashbury

Reputation: 2356

How to give Gatsby a GraphQL schema

We're bringing in some posts from a Wordpress backend, some have pictures (in an ACF field) and some don't. The problem is that Gatsby infers the schema based off of the first node it receives. If it receives a node without a picture, then the schema is wrong.

Where does Gatsby’s GraphQL schema come from? With Gatsby, we use plugins which fetch data from different sources. We then use that data to automatically infer a GraphQL schema.

How can we dictate a schema to GraphQL/Gatsby that always includes a picture, with 'null' as the default value if it's blank?

{
  allWordpressWpTestimonial {
    edges {
      node {
        id
        title
        acf {
          photo_fields {
            photo {
              id
              localFile {
                childImageSharp {
                  sizes {
                    src
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

In the example above, sometimes 'photo' doesn't exist and it breaks everything...

Gatsby config:

const innertext = require('innertext')
const url = require('url')

module.exports = {
  siteMetadata: {
    title: 'Test',
    googleMapsAPIKey: 'xxxxxx',
    adminBaseUrl: '123.123.123',
    adminProtocol: 'http',
  },
  pathPrefix: '/web/beta',
  plugins: [
    'gatsby-plugin-react-next',
    'gatsby-plugin-react-helmet',
    'gatsby-plugin-sharp',
    'gatsby-plugin-svgr',
    {
      resolve: 'gatsby-plugin-google-analytics',
      options: {
        trackingId: 'GOOGLE_ANALYTICS_TRACKING_ID',
      },
    },
    {
      resolve: 'gatsby-plugin-bugherd',
      options: {
        key: 'xxxxxx',
        showInProduction: true,
      },
    },
    {
      resolve: '@andrew-codes/gatsby-plugin-elasticlunr-search',
      options: {
        fields: ['title', 'url', 'textContent', 'urlSearchable'],
        resolvers: {
          wordpress__PAGE: {
            title: node => node.title,
            textContent: node => innertext(node.content),
            url: node => url.parse(node.link).path,
            urlSearchable: node =>
              url
                .parse(node.link)
                .path.split('/')
                .join(' '),
          },
          wordpress__POST: {
            title: node => node.title,
            textContent: node => innertext(node.content),
            url: node => `/news/${node.slug}`,
            urlSearchable: node =>
              url
                .parse(node.link)
                .path.split('/')
                .join(' '),
          },
          wordpress__wp_industry: {
            title: node => node.title,
            textContent: node => innertext(node.content),
            url: node => `/business/industries/${node.slug}`,
            urlSearchable: node =>
              url
                .parse(node.link)
                .path.split('/')
                .join(' '),
          },
        },
      },
    },
    {
      resolve: 'gatsby-source-wordpress',
      options: {
        baseUrl: 'xxxxxx',
        protocol: 'http',
        hostingWPCOM: false,
        useACF: true,
        auth: {
          htaccess_user: 'admin',
          htaccess_pass: 'xxxxxx',
          htaccess_sendImmediately: false,
        },
        verboseOutput: false,
      },
    },
    'gatsby-transformer-sharp',
  ],
}

Upvotes: 16

Views: 8130

Answers (2)

Derek Nguyen
Derek Nguyen

Reputation: 11577

It's been awhile since this post, but since version 2.2 Gatsby has added a new API that'll make it much easier to customize schema. This is not an example with wordpress but with gatsby's gatsby-transformer-remark, but I'm sure it's applicable.

I have a bunch of .md with frontmatter looks like this:

---
title: "Screen title"
image: "./hero-image.png"  <--- sometimes it's an empty string, ""
category: "Cat"
---

...content...

If Gatsby get to the .md with the empty image first, it'll incorrectly infer that field as String, even though it should be File. With the new API, I can tell Gatsby about image field in gatsby-node.js:

exports.sourceNodes = ({ actions, schema }) => {
  const { createTypes } = actions
  createTypes(`
    type MarkdownRemarkFrontmatter {
      image: File
    }

    type MarkdownRemark implements Node {
      frontmatter: MarkdownRemarkFrontmatter
    }
  `)
}

This'll guarantee the image field to always be of File type, otherwise it'll be null.

Some notes:

  • Root nodes like MarkdownRemark has to implement Node
  • A node can implement multiple interface
  • You have to 'work your way' down to the relevant field. In this example, I have to declare the MarkdownRemarkFrontmatter type, then pass it to the frontmatter field in MarkdownRemark node.
  • Gatsby will infer the rest of the fields if not specified. In the example above, since I didn't specify the category field in MarkdownRemarkFrontmatter, it will be inferred by Gatsby just like before.
  • The most helpful way to find these types (MarkdownRemark, MarkdownRemarkFrontmatter) is to look for them in graphiql (default at localhost:8000/___graphql)

Upvotes: 16

Nicholas Cappello
Nicholas Cappello

Reputation: 351

First are you using Gatsby-plugin-sharp, Gatsby-transform-sharp & Gatsby-source-WordPress Plugins ?

My site uses Gatsby-source-Wordpress Plugin plus the sharp library as well as Bluebird for returning promises etc. Define the ImageURL on your Post.js or Page.js. The Source URL is produced when loaded in my Media Library but is offloaded to a S3 bucket because my WordPress site is built "programmatically". The source URL is typically defined by you and can be chosen in ACF field types when build a post of page template.

export const pageQuery = graphql`
  query homePageQuery {
    site {
      siteMetadata {
        title
        subtitle
        description
      }
    }

    allWordpressPost(sort: { fields: [date] }) {
      edges {
        node {
          title
          excerpt
          slug
          type
          _image{
            source_url
          }
          categories {
            slug
            name
          }
        }
      }
    }
  } 

Querying the data in the exact order is a must for each post type or GraphQL will not return the scheme correctly which will produce an error. As simple as it sounds and duplicative, there will have to be two different GraphQL schemes at times and two post.js example post1.js and post2.js files defining the different post categories. 1.Query for the return with Images URL. 2.Query for the return with no Images. to equal null or non-existant That is a downfall of GraphQL it expects to receive X and when Y happens it gets unhappy and fails.

You could also try this when you receive the image transform it with sharp to href= and transform it from https to size it on receiving.But in your case scheme it to be null. We did this for a employee bio page that was return from an old WordPress site.

/**
     * Transform internal absolute link to relative.
     * 
     * @param {string} string The HTML to run link replacemnt on
     */
    linkReplace(string) {
        // console.log(string)
        const formatted = string.replace(
            /(href="https?:\/\/dev-your-image-api\.pantheonsite\.io\/)/g,
            `href="/`
        )

        return formatted
    }

    render() {
        const post = { ...this.props.data.wordpressPost }
        const headshot = { ...this.props.data.file.childImageSharp.resolutions }
        const { percentScrolled } = { ...this.state }
        const contentFormatted = this.linkReplace(post.content)

        return (
            <div ref={el => (this.post = el)}>
                <div className={styles.progressBarWrapper}>
                    <div
                        style={{ width: `${percentScrolled}%` }}
                        className={styles.progressBar}
                    />
                </div>

                <div className={styles.post}>
                    <h1
                        className={styles.title}
                        dangerouslySetInnerHTML={{ __html: post.title }}
                    />

                    <div
                        className={styles.content}
                        dangerouslySetInnerHTML={{ __html: contentFormatted }}
                    />

                    <Bio headshot={headshot} horizontal={true} />
                </div>
            </div>
        )
    }
}

Post.propTypes = {
    data: PropTypes.object.isRequired,
}

export default Post

export const postQuery = graphql`
    query currentPostQuery($id: String!) {
        wordpressPost(id: { eq: $id }) {
            wordpress_id
            title
            content
            slug
        }
        file(relativePath: { eq: "your-image-headshot.jpg" }) {
            childImageSharp {
                resolutions(width: 300, height: 300) {
                    ...GatsbyImageSharpResolutions
                }
            }
        }
    }

`

Hope this helps feel free to message me.

Upvotes: 0

Related Questions