Обратные вызовы запросов GraphQL для Gatsby.js - PullRequest
0 голосов
/ 17 сентября 2018

В Contentful CMS у меня есть два разных типа контента: BigCaseStudy и BigCaseStudySection. Чтобы это содержимое появилось на моем сайте Gatsby 2.x, я подумал:

  1. Выполнить запрос 1, который получает все поля BigCaseStudy, которые я хочу отобразить, а также содержит поле ID содержимого в качестве метаданных.
  2. Взять, что ID из запроса 1, сопоставить со справочным полем Contentful (которое содержит ID) в запросе 2
  3. Выполнить запрос 2, вернуть все соответствующие BigCaseStudySection поля

Конечной целью было бы отобразить оригинал BigCaseStudy со всеми BigCaseStudySection (обычно их число составляет 3-5). Вы можете посмотреть на мои запросы, чтобы увидеть поля, есть куча.

Я думаю, что какая-то комбинация переменных и запросов GraphQL поможет мне (возможно, мутация)? Это не ясно, и я не видел каких-либо сложных примеров запроса одного набора материала и последующего использования ответа для выполнения другого вызова, например, связанных обещаний или async / await в JS.

Есть идеи по правильной конструкции?

bigcasestudy.js компонент с запросами GraphQL:

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 файл:

/**
 * 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
}

Ответы [ 2 ]

0 голосов
/ 19 сентября 2018

Краткий ответ: обратные вызовы с GraphQL не выполняются.Вы делаете один запрос, который получает все, что вам нужно, все сразу.

Более длинный ответ: мне пришлось реконструировать, как файл gatsby-node.js извлекал контентный контент, а затем фильтровал его.В Gatsby вы хотите настроить запросы в gatsby-node.js для извлечения всего из вашего источника данных, потому что это генератор статического сайта.Его архитектура вводит все эти данные и затем анализирует их соответствующим образом.

Запрос GraphQL из моего исходного вопроса был в порядке.Я изменил .then() обещания использовать .filter() в своих результатах, сравнивая поле отношений дочерних узлов с идентификатором родительских узлов.

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
}

После того, как вы это настроите, часть createPage API-интерфейса узла Гэтсби отправляет родителя и все его узлы в componentпараметр, который вы установили.

Внутри моего компонента я теперь могу сделать запрос GraphQL для всех дочерних узлов.Это теперь возвращает то, что я хочу, и согласуется с идеей, что GraphQL делает один запрос вместо нескольких, как я пытался сделать.Единственная сложность заключается в том, что вы должны использовать .map() в части рендеринга компонента, чтобы зациклить все дочерние узлы, отправленные обратно из Contentful.

bigcasestudy.js компонент:

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: спасибо @ taylor-krusen за изменение порядка решения этой проблемы.

0 голосов
/ 17 сентября 2018

Я тоже столкнулся с этой проблемой и не смог найти хорошего решения для этого (хотя я не использовал Contentful), но я справился с этим и думаю, что смогу помочь. Вам нужно немного изменить свое мышление.

По сути, GraphQL на самом деле не предназначен для запроса данных, необходимых для выполнения другого запроса. Это скорее инструмент «спросите, что вам нужно». GraphQL хочет выполнить одиночный запрос для точно , что вам нужно.

Параметр, который вам нужен для запроса, на самом деле взят из вашего файла gatsby-node.js. В частности, свойство context createPages() (API узла, который делает доступным Гэтсби).

Этого достаточно, чтобы вы указали в правильном направлении? Если вам нужно больше руки, то мне нужно знать две вещи:
1. Немного больше контекста вокруг того, что вы пытаетесь достичь. Какие конкретные данные вы хотите получить для конечного пользователя?
2. Как выглядит ваш файл gatsby-node.js.

...