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
},
})
})
}
2
Answers
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
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.
Now I can dynamically create pages with Strapi and the "blocks" I made using dynamic zones.
The problem is here:
You can’t access directly to
Blocks
because younode
is an array insideedges
property. From what I see, the loop is done inBlocksRenderer
hence you need to provide it an array ofblocks
. Without knowing exactly your data structure and what returns it’s difficult to guess but try something like:If your
Home.js
query is using a page query instead of a static query they cane be triggered and hydrated in different runtimes and build times, so one can fail if the other doesn’t. This leads me to think that maybe the query is ok, but the logic isn’t. You can easily check it by adding a simple condition like:Or: