I'm attempting to query for a specific blog post using the slug - so that a specific post is shown depending on the slug.
I have encountered the following error: TypeError: Cannot read property 'Title' of null
This is the logic to the below code:
eq
(equals) the variable $slug
data.sanityPosts.Title
Title
is undefined for some reasonexport const query = graphql` {
sanityPosts(slug: {current: {eq: "$slug"}}) {
Title
}
}
`;
const singlePost = ({ data }) => (
<Layout>
<Header />
<div>
<h1>{data.sanityPosts.Title}</h1>
</div>
<Footer />
</Layout>
)
export default singlePost
Why is Title
undefined? And how can I approach this in a better way?
Your component should look like:
export const query = graphql` {
sanityPosts(slug: {current: {eq: "$slug"}}) {
Title
}
}
`;
const SinglePost = ({ data }) => (
<Layout>
<Header />
<div>
<h1>{data.sanityPosts.Title}</h1>
</div>
<Footer />
</Layout>
)
export default SinglePost
Notice the capitalized S
in SinglePost
.
Said that, the only way to pass the $slug
to a template/page component is using the context API, which is basically what you do in the gatsby-node.js
. There, you should query for all posts and use the createPages API while iterate through them to create dynamic pages. There, you will expose the context for each post, and you can tell Gatsby where this template is located in order to pass data.
The answer lacks of information, but the workaround should look like:
const path = require(`path`)
const { createFilePath } = require(`gatsby-source-filesystem`)
exports.onCreateNode = ({ node, getNode, actions }) => {
const { createNodeField } = actions
if (node.internal.type === `MarkdownRemark`) {
const slug = createFilePath({ node, getNode, basePath: `pages` })
createNodeField({
node,
name: `slug`,
value: slug,
})
}
}
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions
const result = await graphql(`
query {
allMarkdownRemark {
edges {
node {
fields {
slug
}
}
}
}
}
`)
result.data.allMarkdownRemark.edges.forEach(({ node }) => {
createPage({
path: node.fields.slug,
component: path.resolve(`./src/templates/blog-post.js`),
context: {
// Data passed to context is available
// in page queries as GraphQL variables.
slug: node.fields.slug,
},
})
})
}
Note: of course, adapt the markdown approach (allMarkdownRemark
) for your Sanity one
Now you can access to the slug
in your template:
import React from "react"
import { graphql } from "gatsby"
import Layout from "../components/layout"
export default function BlogPost({ data }) {
const post = data.markdownRemark
return (
<Layout>
<div>
<h1>{post.frontmatter.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.html }} />
</div>
</Layout>
)
}
export const query = graphql`
query($slug: String!) {
markdownRemark(fields: { slug: { eq: $slug } }) {
html
frontmatter {
title
}
}
}
`
Note: again, replace the markdown approach for your Sanity
You can follow the full guide at Gatsby's docs: https://www.gatsbyjs.com/docs/tutorial/part-seven/
Keep also in mind, that you have exposed the GraphQL playground at localhost:8000/___graphql
to test your queries in order to know-how are the fields named or how the nested structure should look like, that will save you to know how's title
field named.