简体   繁体   中英

GatsbyJS having issues rendering GraphQL queries from other components

I'm using Strapi as a Headless CMS and building my frontend with Gatsby + Graphql. I have a "blocks renderer" component that is rendering any of the dynamic zones in strapi.

import React from "react"
import { graphql } from "gatsby"
import BlockHero from "./block-hero"
import BlockParagraph from "./block-paragraph"
import BlockSplitFeature from "./block-split-feature"

const componentsMap = {
    // STRAPI__COMPONENT_LAYOUT_ELEMENTS_MULTIPLE_CALLOUT: blockMultipleCallout,
    STRAPI__COMPONENT_LAYOUT_ELEMENTS_SIMPLE_PARAGRAPH: BlockParagraph,
    STRAPI__COMPONENT_LAYOUT_ELEMENTS_SPLIT_FEATURE: BlockSplitFeature,
    STRAPI__COMPONENT_MEDIA_ELEMENT_HERO: BlockHero,
    // STRAPI__COMPONENT_META_DATA_DEFAULT_SEO: blockSeo
}

const Block = ({ block }) => {
    const Component = componentsMap[block.__typename]

    if(!Component) {
        return null
    }

    return <Component data={block} />
}

const BlocksRenderer = ({ blocks }) => {
    return (
        <div>
            {blocks.map((block, index) => (
                <Block key={`${index}${block.__typename}`} block={block} />
            ))}
        </div>
    )
}

export const query = graphql`
    fragment Blocks on STRAPI__COMPONENT_LAYOUT_ELEMENTS_CTASTRAPI__COMPONENT_LAYOUT_ELEMENTS_MULTIPLE_CALLOUTSTRAPI__COMPONENT_LAYOUT_ELEMENTS_SIMPLE_PARAGRAPHSTRAPI__COMPONENT_LAYOUT_ELEMENTS_SPLIT_FEATURESTRAPI__COMPONENT_MEDIA_ELEMENT_HEROUnion {
        __typename
        ... on STRAPI__COMPONENT_LAYOUT_ELEMENTS_MULTIPLE_CALLOUT {
            id
            MultipleCalloutItem {
                id
                Heading
                Description
            }
          }
          ... on STRAPI__COMPONENT_LAYOUT_ELEMENTS_SIMPLE_PARAGRAPH {
            id
            Text
          }
          ... on STRAPI__COMPONENT_LAYOUT_ELEMENTS_SPLIT_FEATURE {
            id
            Heading
            Description
            mediaAlignment
            Media {
                id
                mime
                localFile {
                    childImageSharp {
                        gatsbyImageData
                      }
                }
                alternativeText
            }
          }
          ... on STRAPI__COMPONENT_MEDIA_ELEMENT_HERO {
            id
            Heading
            Description
            Media {
                id
                mime
                alternativeText
                localFile {
                    url
                }
                alternativeText
            }
          }
    }
`

export default BlocksRenderer

Then I have my page layout file to generate a page layout (side note, the "Layout" element is just for the navigation & footer. This will be rewritten once I have this page layout file issue fixed)>

import React from "react"
import { useStaticQuery, graphql } from "gatsby"
import Layout from "../components/layout"
import Seo from "../components/seo"
import BlocksRenderer from "../components/blocks-renderer"

const PageLayout = () => {
    const { allStrapiPage } = useStaticQuery(graphql`
        query {
            allStrapiPage {
                edges {
                    node {
                        id
                        Name
                        Slug
                        Blocks {
                            ...Blocks
                        }
                    }
                }
            }
        }
    `)
    const { Blocks } = allStrapiPage

    return (
        <Layout>
            <div>{allStrapiPage.id}</div>
            <h1>{allStrapiPage.Name}</h1>
            <BlocksRenderer blocks={allStrapiPage.Blocks} />
        </Layout>
    )
}

export default PageLayout

I'm dynamically creating pages with a gatsby-node.js file. When I try to access one of the dynamically created slugs, I get an error in the blocks-renderer file that says can't access property "map", blocks is undefined. Anyone have any ideas?

EDIT: Added the additional files mentioned.

gatsby-config.js file below:

/**
 * Configure your Gatsby site with this file.
 *
 * See: https://www.gatsbyjs.com/docs/gatsby-config/
 */

require("dotenv").config({
  path: `.env.${process.env.NODE_ENV}`,
})

module.exports = {
  /* Your site config here */
  plugins: [
    "gatsby-plugin-gatsby-cloud",
    "gatsby-plugin-postcss",
    "gatsby-plugin-sass",
    "gatsby-plugin-image",
    "gatsby-plugin-sharp",
    "gatsby-transformer-sharp",
    "gatsby-transformer-remark",
    {
      resolve: "gatsby-source-strapi",
      options: {
        apiURL: process.env.STRAPI_API_URL || "http://localhost:1337",
        accessToken: process.env.STRAPI_TOKEN,
        collectionTypes: [ 
        "drink", 
        "category", 
        {
          singularName: "page",
          queryParams: {
            populate: {
              Blocks: {
                populate: "*",
                MultipleCalloutItem: {
                  populate: "*",
                },
              },
              PageMeta: {
                populate: "*",
              },
              ParentPage: {
                populate: "*",
              },
            },
          },
        },
      ],
        singleTypes: [
          {
            singularName: "global",
            queryParams: {
              populate: {
                DefaultSeo: {
                  populate: "*",
                },
                Favicon: {
                  populate: "*",
                },
              },
            },
          },
          {
            singularName: "homepage",
            queryParams: {
              populate: {
                Blocks: {
                  populate: "*",
                },
              },
            },
          },
        ],
        queryLimit: 1000,
      }
    },
  ],
}

home.js (which works as intended).

import React from "react"
import { useStaticQuery, graphql } from "gatsby"
import Layout from "../components/layout"
import Seo from "../components/seo"
import BlocksRenderer from "../components/blocks-renderer"

const HomePage = () => {
    const { strapiHomepage } = useStaticQuery(graphql`
        query {
            strapiHomepage {
                Blocks {
                    ...Blocks
                }
            }
        }
    `)
    const { Blocks } = strapiHomepage

    // const seo = {
    //     metaTitle: title,
    //     metaDescription: title
    // }

    return (
        <Layout>
            <BlocksRenderer blocks={Blocks} />
        </Layout>
    )
}

export default HomePage

This is the gatsby-node.js file I'm using to generate the pages with the page-layout.js file. Note that I can generate the pages and content, minus the Blocks query.

const path = require('path')

exports.createPages = async ({ graphql, actions, reporter }) => {
    const { createPage } = actions

    const result = await graphql(
        `
        query {
            allStrapiPage {
                edges {
                    node {
                        Slug
                        Name
                        ParentPage {
                            Slug
                        }
                    }
                }
            }
        }
    `
    )

    if (result.errors) {
        reporter.panicOnBuild(`Error while running GraphQL Query`)
        return
    }

    const pageTemplate = path.resolve(`./src/layouts/page-layout.js`)
    result.data.allStrapiPage.edges.forEach(({ node }) => {
        const path = node.Slug
        createPage({
            path,
            component: pageTemplate,
            context: {
                pagePath: path
            },
        })
    })
}

The problem is here:

<BlocksRenderer blocks={allStrapiPage.Blocks} />

You can't access directly to Blocks because you node is an array inside edges property. From what I see, the loop is done in BlocksRenderer hence you need to provide it an array of blocks . Without knowing exactly your data structure and what returns it's difficult to guess but try something like:

<BlocksRenderer blocks={allStrapiPage.edges.node[0].Blocks} />

Thanks to @Ferran I was pointed in the right direction and solved this issue.

Two changes needed to be made for this to work properly. First, I needed to be passing pageContext from the gatsby-node.js file. Here I'm passing the page slug to the template

const { resolve } = require('path')
const path = require('path')

exports.createPages = async ({ graphql, actions, reporter }) => {
    const { createPage } = actions

    const result = await graphql(
        `
        query {
            allStrapiPage {
                edges {
                    node {
                        Slug
                        Name
                        ParentPage {
                            Slug
                        }
                    }
                }
            }
        }
    `
    )

    if (result.errors) {
        reporter.panicOnBuild(`Error while running GraphQL Query`)
        return
    }

    const pageTemplate = path.resolve('./src/layouts/page-layout.js')
    result.data.allStrapiPage.edges.forEach(edge => {
        const path = `${edge.node.Slug}`
        const parentPath = `${edge.node.ParentPage.Slug}`
        createPage({
            path,
            component: pageTemplate,
            context: {
                Slug: edge.node.Slug
            },
        })
        resolve()
    })
}

Then in the page-layout.js file, I needed to get the pageContext from gatsby-node.js, map all of my page nodes, in the graphql query, and pass the page Slug from gatsby-node.js as a variable in the graphql query.

import React from "react"
import { useStaticQuery, graphql } from "gatsby"
import Layout from "../components/layout"
import Seo from "../components/seo"
import BlocksRenderer from "../components/blocks-renderer"

const PageLayout = ({ data, pageContext }) => {
    const { Slug } = pageContext

    console.log(Slug)

    return (
        <Layout>
            {
                data.allStrapiPage.nodes.map(node => {
                    return (
                        <div key={node.id}>
                        <h1>{node.Name}</h1>
                        {node.Blocks &&
                            <BlocksRenderer blocks={node.Blocks} />
                        }
                    </div>
                    )
                    
                })
            }
        </Layout>
    )
}

export const query = graphql`
        query GetPage($Slug: String) {
            allStrapiPage(filter: { Slug: {in: [$Slug]} }) {
                nodes {
                    id
                    Name
                    Blocks {
                        ...Blocks
                    }
                }
            }
        }
    `

export default PageLayout

Now I can dynamically create pages with Strapi and the "blocks" I made using dynamic zones.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM