Search code examples
reactjsgraphqlcontent-management-systemgatsbysanity

Gatsby Query for specific post using slug


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:

  1. specify slug in my filter
  2. then eq (equals) the variable $slug
  3. the above variable will allow me to dynamically filter, instead of manually making a query for every post in the backend
  4. I then query the data on the front end with data.sanityPosts.Title
  5. Title is undefined for some reason
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

Why is Title undefined? And how can I approach this in a better way?


Solution

  • 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.