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.