GraphQL query callbacks for Gatsby.js

家住魔仙堡 提交于 2019-12-11 08:52:48

问题


In the Contentful CMS, I have two different content-types: BigCaseStudy and BigCaseStudySection. To get this content to appear in my Gatsby 2.x site, my thinking was:

  1. Do query 1, which gets all the BigCaseStudy fields I want to display, and also contains the content's ID field as metadata.
  2. Take that ID from query 1, match to a Contentful reference field (which contains an ID) in query 2
  3. Do query 2, return all matching BigCaseStudySection fields

The end goal would be to display the original BigCaseStudy with all of the BigCaseStudySection (usually numbering between 3-5 of them). You can look at my queries to see the fields, there are bunch.

I think some combination of GraphQL variables and queries would get me there (maybe a mutation)? It's not clear and I haven't seen any complex examples of querying one set of stuff and then using the response to make another call, like chained promises or async/await in JS.

Any ideas on the right construction?

bigcasestudy.js component with GraphQL queries:

import React from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'

import { graphql } from 'gatsby'
import Img from 'gatsby-image'

import Layout from '../Layout/layout'

/** 
 * Hero Section
 */ 
const HeroContainer = styled.header`
  align-items: center;
  background-image: url(${ props => props.bgImgSrc });
  background-position: center center;
  background-size: cover;
  display: flex;
  flex-direction: column;
  justify-content: center;
  height: calc(100vh - 128px);
`
const HeroTitle = styled.h1`
  color: #fff;
  font-size: 70px;
  font-weight: 700;
  letter-spacing: 2px;
  margin-bottom: 15px;
`
const HeroSubtitle = styled.h2`
  color: #fff;
  font-size: 24px;
  font-weight: 300;
  letter-spacing: 5px;
  text-transform: uppercase;
`
/** 
 * Intro Section
 */ 
const IntroBG = styled.section`
  background-color: ${ props => props.theme.lightGray };
  padding: 50px 0;
`
const IntroContainer = styled.div`
  padding: 25px;
  margin: 0 auto;
  max-width: ${ props => props.theme.sm };

  @media (min-width: ${ props => props.theme.sm }) {
    padding: 50px 0;
  }
`
const IntroTitle = styled.h2`
  font-size: 50px;
  font-weight: 700;
  letter-spacing: 2px;
  margin-bottom: 45px;
  text-align: center;
`
const IntroText = styled.p`
  font-size: 22px;
  line-spacing: 4;
  text-align: center;
`

const IntroButton = styled.a`
  background-color: #fff;
  color: ${ props => props.theme.darkGray };
  border: 1px solid ${ props => props.theme.mediumGray };
  border-radius: 25px;
  display: block;
  font-size: 16px;
  letter-spacing: 5px;
  margin: 30px auto;
  padding: 15px 45px;
  text-align: center;
  text-decoration: none;
  text-transform: uppercase;
  width: 300px;
`

// BigCaseStudy Component
class BigCaseStudy extends React.Component {
  render() {
    // Setup destructured references to each Contentful object passed in through the GraphQL call
    const { caseStudyTitle } = this.props.data.contentfulBigCaseStudy
    const { caseStudySubtitle } = this.props.data.contentfulBigCaseStudy
    const { caseStudyIntroTitle } = this.props.data.contentfulBigCaseStudy
    const { caseStudyIntro } = this.props.data.contentfulBigCaseStudy.caseStudyIntro
    const { caseStudyLink } = this.props.data.contentfulBigCaseStudy

    console.log(this)

    return (
      <Layout>
        <HeroContainer 
          bgImgSrc={ this.props.data.contentfulBigCaseStudy.caseStudyHero.fixed.src }>
          <HeroTitle>{ caseStudyTitle }</HeroTitle>
          <HeroSubtitle>{ caseStudySubtitle }</HeroSubtitle>
        </HeroContainer>
        <IntroBG>
          <IntroContainer>
            <IntroTitle>{ caseStudyIntroTitle }</IntroTitle>
            <IntroText>{ caseStudyIntro }</IntroText>
          </IntroContainer>
          <IntroButton href={ caseStudyLink } target="_blank" rel="noopener noreferrer">
            Visit the site >
          </IntroButton>
        </IntroBG>
      </Layout>
    )
  }
}

// Confirm data coming out of contentful call is an object
BigCaseStudy.propTypes = {
  data: PropTypes.object.isRequired
}

// Export component
export default BigCaseStudy

// Do call for the page data
// This needs to mirror how you've set up the dynamic createPage function data in gatsby-node.js
export const BigCaseStudyQuery = graphql`
  query BigCaseStudyQuery {
    contentfulBigCaseStudy {
      id
      caseStudyTitle
      caseStudySubtitle
      caseStudyIntroTitle
      caseStudyIntro {
        caseStudyIntro
      }
      caseStudyLink
      caseStudyHero {
        fixed {
          width
          height
          src
          srcSet
        }                  
      }
    },
    contentfulBigCaseStudySection (id: $postId) {
      title
      order
      images {
        fixed {
          width
          height
          src
          srcSet
        }
      }
      bigCaseStudyReference {
        id
      }
      body {
        body
      }
      stats {
        stat1 {
          word
          number
        }
        stat2 {
          word
          number
        }
        stat3 {
          word
          number
        }
        stat4 {
          word
          number
        } 
      }
      id
    }
  }
`

gatsby-node.js file:

/**
 * Implement Gatsby's Node APIs in this file.
 *
 * ######################################################
 * BIG CASE STUDY BACKEND CODE
 * ######################################################
 * 
 * We are using the .createPages part of the Gatsby Node API: https://next.gatsbyjs.org/docs/node-apis/#createPages 
 * What this does is dynamically create pages (surprise) based on the data you feed into it
 * 
 * Feed the contentful API call into the promise
 * Here I'm calling BigCaseStudy, which is a custom content type set up in contentful
 * This is briefly explained over here: https://www.gatsbyjs.org/packages/gatsby-source-contentful/
 * 
 * Also, note the caseStudyIntro field is long text `markdown`
 * Gatsby returns the long text field as an object
 * Calling it's name inside of the object returns the HTML
 * Read more here: https://github.com/gatsbyjs/gatsby/issues/3205
 */

// Set Gatsby path up to be used by .createPages
const path = require('path')

// Using Node's module export, Gatsby adds in a createPages factory 
exports.createPages = ({ graphql, actions }) => {

  // We setup the createPage function takes the data from the actions object
  const { createPage } = actions

  // Setup a promise to build pages from contentful data model for bigCaseStudies
  return new Promise((resolve, reject) => {

    // Setup destination component for the data
    const bigCaseStudyComponent = path.resolve('src/components/BigCaseStudy/bigcasestudy.js')

    resolve(
      graphql(`
        {
          allContentfulBigCaseStudy {
            edges {
              node { 
                id
                caseStudySlug
                caseStudyTitle
                caseStudySubtitle
                caseStudyIntroTitle
                caseStudyIntro {
                  caseStudyIntro
                }
                caseStudyLink
                caseStudyHero {
                  fixed {
                    width
                    height
                    src
                    srcSet
                  }                  
                }
              }
            }
          }
          allContentfulBigCaseStudySection {
            edges {
              node {
                title
                order
                images {
                  fixed {
                    width
                    height
                    src
                    srcSet
                  }
                }
                bigCaseStudyReference {
                  id
                }
                body {
                  body
                }
                stats {
                  stat1 {
                    word
                    number
                  }
                  stat2 {
                    word
                    number
                  }
                  stat3 {
                    word
                    number
                  }
                  stat4 {
                    word
                    number
                  } 
                }
                id
              }
            }
          }
        }
      `).then((result) => {

        // Now we loop over however many caseStudies Contentful sent back
        result.data.allContentfulBigCaseStudy.edges.forEach((caseStudy) => {
          const caseStudySections = result.data.allContentfulBigCaseStudySection.edges

          let caseStudySectionMatches = caseStudySections.filter( 
            caseStudySection => caseStudySection.bigCaseStudyReference.id === caseStudy.id 
          )

          createPage ({
            path: `/work/${caseStudy.node.caseStudySlug}`,
            component: bigCaseStudyComponent,
            context: {
              id: caseStudy.node.id,
              slug: caseStudy.node.caseStudySlug,
              title: caseStudy.node.caseStudyTitle,
              subtitle: caseStudy.node.caseStudySubtitle,
              hero: caseStudy.node.caseStudyHero,
              introTitle: caseStudy.node.caseStudyIntroTitle,
              intro: caseStudy.node.caseStudyIntro.caseStudyIntro,
              link: caseStudy.node.caseStudyLink,
              caseStudySection: caseStudySectionMatches.node
            }
          })

        })
      })

      // This is the error handling for the calls
      .catch((errors) => {
        console.log(errors)
        reject(errors)
      })

    ) // close resolve handler
  }) // close promise
}

回答1:


I ran into this challenge too and couldn't find a good solution to accomplish that (although I wasn't using Contentful), but I did work past it and think I can help. You'll need to shift your thinking a bit.

Basically, GraphQL isn't really meant to query for the data you need to run another query. It's more of a 'ask for what you need' sort of tool. GraphQL wants to run a single query for exactly what you need.

The parameter you need for your query actually comes from your gatsby-node.js file. Specifically, the context property of createPages()(a Node API that gatsby makes available).

Is that enough to get you pointed in the right direction? If you need a bit more of a hand, then there are two things I need to know:
1. A little more context around what you're trying to accomplish. What is the specific data you want available to the end user?
2. What your gatsby-node.js file looks like.




回答2:


Short answer: you don't do callbacks with GraphQL. You do one query that gets everything you need all at once.

Longer answer: I had to reconstruct how the gatsby-node.js file fetched Contentful content and then filtered through it. In Gatsby, you want to set up the queries in gatsby-node.js to go fetch everything from your data source because it's a static site generator. It's architecture brings all that data in and then parses it out accordingly.

The GraphQL query from my original question was fine. I changed .then() of the promise to use .filter() on my results, comparing the relationship field of the child nodes to the id of the parent nodes.

gatsby-node.js:

// Set Gatsby path up to be used by .createPages
const path = require('path')

// Using Node's module export, Gatsby adds in a createPages factory 
exports.createPages = ({ graphql, actions }) => {

  // We setup the createPage function takes the data from the actions object
  const { createPage } = actions

  // Setup a promise to build pages from contentful data model for bigCaseStudies
  return new Promise((resolve, reject) => {

    // Setup destination component for the data
    const bigCaseStudyComponent = path.resolve('src/components/BigCaseStudy/bigcasestudy.js')

    resolve(
      graphql(`
        {
          allContentfulBigCaseStudy {
            edges {
              node { 
                id
                caseStudySlug
                caseStudyTitle
                caseStudySubtitle
                caseStudyIntroTitle
                caseStudyIntro {
                  caseStudyIntro
                }
                caseStudyLink
                caseStudyHero {
                  fixed {
                    width
                    height
                    src
                    srcSet
                  }                  
                }
              }
            }
          }
          allContentfulBigCaseStudySection {
            edges {
              node {
                title
                order
                images {
                  fixed {
                    width
                    height
                    src
                    srcSet
                  }
                }
                bigCaseStudyReference {
                  id
                }
                body {
                  body
                }
                stats {
                  stat1 {
                    word
                    number
                  }
                  stat2 {
                    word
                    number
                  }
                  stat3 {
                    word
                    number
                  }
                  stat4 {
                    word
                    number
                  } 
                }
                id
              }
            }
          }
        }
      `).then((result) => {

        // Now we loop over however many caseStudies Contentful sent back
        result.data.allContentfulBigCaseStudy.edges.forEach((caseStudy) => {
          let matchedCaseStudySections = result.data.allContentfulBigCaseStudySection.edges.filter(
            caseStudySection => 
              caseStudySection.node.bigCaseStudyReference.id === caseStudy.node.id 
          )

          createPage ({
            path: `/work/${caseStudy.node.caseStudySlug}`,
            component: bigCaseStudyComponent,
            context: {
              id: caseStudy.node.id,
              slug: caseStudy.node.caseStudySlug,
              title: caseStudy.node.caseStudyTitle,
              subtitle: caseStudy.node.caseStudySubtitle,
              hero: caseStudy.node.caseStudyHero,
              introTitle: caseStudy.node.caseStudyIntroTitle,
              intro: caseStudy.node.caseStudyIntro.caseStudyIntro,
              link: caseStudy.node.caseStudyLink,
              caseStudySection: matchedCaseStudySections.node
            }
          })

        })
      })

      // This is the error handling for the calls
      .catch((errors) => {
        console.log(errors)
        reject(errors)
      })

    ) // close resolve handler
  }) // close promise
}

Once you've set this up, the createPage part of Gatsby Node API sends the parent and all of it's nodes over to the component param you set.

Inside of my component, I can now make a GraphQL query for all children nodes. That now returns what I want and conforms to the idea that GraphQL makes one request instead of multiple like I was trying to do. The only tricky part is that you have to use .map() in the render part of the component to loop over all the child nodes sent back from Contentful.

bigcasestudy.js component:

import React from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'

import { graphql } from 'gatsby'
import Img from 'gatsby-image'

import Layout from '../Layout/layout'

/** 
 * Hero Section
 */ 
const HeroContainer = styled.header`
  align-items: center;
  background-image: url(${ props => props.bgImgSrc });
  background-position: center center;
  background-size: cover;
  display: flex;
  flex-direction: column;
  justify-content: center;
  height: calc(100vh - 128px);
`
const HeroTitle = styled.h1`
  color: #fff;
  font-size: 70px;
  font-weight: 700;
  letter-spacing: 2px;
  margin-bottom: 15px;
`
const HeroSubtitle = styled.h2`
  color: #fff;
  font-size: 24px;
  font-weight: 300;
  letter-spacing: 5px;
  text-transform: uppercase;
`
/** 
 * Intro Section
 */ 
const IntroBG = styled.section`
  background-color: ${ props => props.theme.lightGray };
  padding: 50px 0;
`
const IntroContainer = styled.div`
  padding: 25px;
  margin: 0 auto;
  max-width: ${ props => props.theme.sm };

  @media (min-width: ${ props => props.theme.sm }) {
    padding: 50px 0;
  }
`
const IntroTitle = styled.h2`
  font-size: 50px;
  font-weight: 700;
  letter-spacing: 2px;
  margin-bottom: 45px;
  text-align: center;
`
const IntroText = styled.p`
  font-size: 22px;
  line-spacing: 4;
  text-align: center;
`

const IntroButton = styled.a`
  background-color: #fff;
  color: ${ props => props.theme.darkGray };
  border: 1px solid ${ props => props.theme.mediumGray };
  border-radius: 25px;
  display: block;
  font-size: 16px;
  letter-spacing: 5px;
  margin: 30px auto;
  padding: 15px 45px;
  text-align: center;
  text-decoration: none;
  text-transform: uppercase;
  width: 300px;
`



// BigCaseStudy Component
class BigCaseStudy extends React.Component {
  render() {

    // Destructure Case Study Intro stuff
    const { 
      caseStudyHero, 
      caseStudyIntro, 
      caseStudyIntroTitle, 
      caseStudyLink, 
      caseStudySubtitle, 
      caseStudyTitle 
    } = this.props.data.contentfulBigCaseStudy

    // Setup references to Case Study Sections, destructure BigCaseStudySection object
    const caseStudySections = this.props.data.allContentfulBigCaseStudySection.edges.map( 
      (currentSection) => {
        return currentSection.node
      }
    )

    // Case Study Section can be in any order, so we need to sort them out
    const caseStudySectionsSorted = caseStudySections.sort( (firstItem, secondItem) => {
      return firstItem.order > secondItem.order ? 1 : -1
    })

    console.log(caseStudySectionsSorted)

    return (
      <Layout>
        <HeroContainer 
          bgImgSrc={ caseStudyHero.fixed.src }>
          <HeroTitle>{ caseStudyTitle }</HeroTitle>
          <HeroSubtitle>{ caseStudySubtitle }</HeroSubtitle>
        </HeroContainer>
        <IntroBG>
          <IntroContainer>
            <IntroTitle>{ caseStudyIntroTitle }</IntroTitle>
            <IntroText>{ caseStudyIntro.caseStudyIntro }</IntroText>
          </IntroContainer>
          <IntroButton href={ caseStudyLink } target="_blank" rel="noopener noreferrer">
            Visit the site >
          </IntroButton>
        </IntroBG>
        {
          caseStudySectionsSorted.map( (caseStudySection, index) => {
            return <IntroTitle key={ index }>{ caseStudySection.title }</IntroTitle>
          })
        }
      </Layout>
    )
  }
}

// Confirm data coming out of contentful call is an object
BigCaseStudy.propTypes = {
  data: PropTypes.object.isRequired
}

// Export component
export default BigCaseStudy

// Do call for the page data
// This needs to mirror how you've set up the dynamic createPage function data in gatsby-node.js
export const BigCaseStudyQuery = graphql`
  query BigCaseStudyQuery {
    contentfulBigCaseStudy {
      id
      caseStudyTitle
      caseStudySubtitle
      caseStudyIntroTitle
      caseStudyIntro {
        caseStudyIntro
      }
      caseStudyLink
      caseStudyHero {
        fixed {
          width
          height
          src
          srcSet
        }                  
      }
    }
    allContentfulBigCaseStudySection {
      edges {
        node {
          title
          order
          images {
            fixed {
              width
              height
              src
              srcSet
            }
          }
          bigCaseStudyReference {
            id
          }
          body {
            body
          }
          stats {
            stat1 {
              word
              number
            }
            stat2 {
              word
              number
            }
            stat3 {
              word
              number
            }
            stat4 {
              word
              number
            } 
          }
          id
        }
      }
    }
  }
`

H/t: thanks to @taylor-krusen for rearranging how I was approaching this problem.



来源:https://stackoverflow.com/questions/52360940/graphql-query-callbacks-for-gatsby-js

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!