Reputation: 58
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
},
})
})
}
Upvotes: 3
Views: 687
Reputation: 58
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.
Upvotes: 0
Reputation: 29320
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} />
I have a
Home.js
file that is using a different property (allStrapiHomepage
) andBlockRenderer
is working as expected
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:
<BlocksRenderer blocks={allStrapiPage?.Blocks} />
Or:
{allStrapiPage.Blocks && <BlocksRenderer blocks={allStrapiPage.Blocks} />}
Upvotes: 1