H.Tibbs
H.Tibbs

Reputation: 21

Displaying contents of directory(markdown files) in Gatsby using graphql

I'm using Gatsby/Netlify CMS stack and have been trying to display markdown file contents on the main page. For example, I have a directory in src/pages/experience that displays all of the experience markdown files.

So using graphql, I have a query that actually works:

{
        allMarkdownRemark(
         limit: 3,
         sort: { order: DESC, fields: [frontmatter___date] },
         filter: { fileAbsolutePath: { regex: "/(experience)/" } }
       ) {
           edges {
             node {
               id
               frontmatter {
                 title
                 company_role
                 location
                 work_from
                 work_to
                 tags
               }
               excerpt
             }
           }
         }
     }

However, when running it on my component page it displays × TypeError: Cannot read property 'allMarkdownRemark' of undefined

However after entering this before return:

if (!data) { return null };

The error goes away but the entire section disappears. Here it is below:

const Experience = ({data}) => {

return (

    <div id="experience" className="section accent">
              <div className="w-container">
                  <div className="section-title-group">
                    <Link to="#experience"><h2 className="section-heading centered">Experience</h2></Link>
                  </div>
              <div className="columns w-row">
                     {data.allMarkdownRemark.edges.map(({node}) => (
                        <div className="column-2 w-col w-col-4 w-col-stack" key={node.id}>
                            <div className="text-block"><strong>{node.frontmatter.title}</strong></div>
                            <div className="text-block-4">{node.frontmatter.company_role}</div>
                            <div className="text-block-4">{node.frontmatter.location}</div>
                            <div className="text-block-3">{node.frontmatter.work_from} – {node.frontmatter.work_to}</div>
                            <p className="paragraph">{node.frontmatter.excerpt}</p>
                            <div className="skill-div">{node.frontmatter.tags}</div>
                        </div>
                     ))} 
              </div>
          </div>
      </div>
)}

export default Experience

In gatsby-config-js, I've added a gatsby-source-filesystem resolve separate from /src/posts to /src/pages where the experience directory is src/pages/experience.

Update: 2/7/2019 Here is the gatsby-config-js file:

module.exports = {
  siteMetadata: {
    title: `Howard Tibbs Portfolio`,
    description: `This is a barebones template for my portfolio site`,
    author: `Howard Tibbs III`,
    createdAt: 2019
  },
    plugins: [
      `gatsby-plugin-react-helmet`,
      {
        resolve: `gatsby-source-filesystem`,
        options: {
          name: `images`,
          path: `${__dirname}/src/images`,
          },
      },
      {
        resolve: 'gatsby-transformer-remark',
        options: {
          plugins: [
            {
              resolve: 'gatsby-remark-images',
            },
          ],
        },
      },
      {
        resolve: `gatsby-source-filesystem`,
        options: {
          name: `posts`,
          path: `${__dirname}/src/posts`,
          },
      },
      {
        resolve: `gatsby-source-filesystem`,
        options: {
          name: `pages`,
          path: `${__dirname}/src/pages`,
          },
      },
        `gatsby-plugin-netlify-cms`,
        `gatsby-plugin-sharp`,
        {
          resolve: `gatsby-plugin-manifest`,
          options: {
            name: `gatsby-starter-default`,
            short_name: `starter`,
            start_url: `/`,
            background_color: `#663399`,
            theme_color: `#663399`,
            display: `minimal-ui`,
            icon: `src/images/gatsby-icon.png`, // This path is relative to the root of the site.
          },
        },
        `gatsby-transformer-sharp`
    ],
  }

What I feel is that somewhere in gatsby-node-js, I did not create a instance to do something with that type query.

const path = require('path')
const { createFilePath } = require('gatsby-source-filesystem')

const PostTemplate = path.resolve('./src/templates/post-template.js')
const BlogTemplate = path.resolve('./src/templates/blog-template.js')

exports.onCreateNode = ({ node, getNode, actions }) => {
    const { createNodeField } = actions
    if (node.internal.type === 'MarkdownRemark') {
        const slug = createFilePath({ node, getNode, basePath: 'posts' })
    createNodeField({
        node,
        name: 'slug',
        value: slug,
    })

}
}

exports.createPages = async ({ graphql, actions }) => {

const { createPage } = actions
const result = await graphql(`
    {
        allMarkdownRemark (limit: 1000) {
          edges {
            node {
              fields {
                slug
              }

            }
          }
        }
      }
`)

const posts = result.data.allMarkdownRemark.edges

posts.forEach(({ node: post }) => {
    createPage({
        path: `posts${post.fields.slug}`,
        component: PostTemplate,
        context: {
            slug: post.fields.slug,
        },
    })
})

const postsPerPage = 2
const totalPages = Math.ceil(posts.length / postsPerPage)

Array.from({ length: totalPages }).forEach((_, index) => {
    const currentPage = index + 1
    const isFirstPage = index === 0
    const isLastPage = currentPage === totalPages

    createPage({
        path: isFirstPage ? '/blog' : `/blog/${currentPage}`,
        component: BlogTemplate,
        context: {
            limit: postsPerPage,
            skip: index * postsPerPage,
            isFirstPage,
            isLastPage,
            currentPage,
            totalPages,
        },
    })
})
}

Wanted to know if anyone was able to get something similar to work? Greatly appreciate your help.


Update: 2/6/2019

So made some changes to my code from pageQuery to StaticQuery and unfortunately it still doesn't work but I believe it is going the right direction:

export default() => (

    <div id="experience" className="section accent">
              <div className="w-container">
                  <div className="section-title-group">
                    <Link to="#experience"><h2 className="section-heading centered">Experience</h2></Link>
                  </div>
              <div className="columns w-row">
              <StaticQuery
              query={graphql`
                  query ExperienceQuery {
                      allMarkdownRemark(
                       limit: 2,
                       sort: { order: DESC, fields: [frontmatter___date]},
                       filter: {fileAbsolutePath: {regex: "/(experience)/"}}
                     ) {
                         edges {
                           node {
                             id
                             frontmatter {
                               title
                               company_role
                               location
                               work_from
                               work_to
                               tags
                             }
                             excerpt
                           }
                         }
                       }
                   }

              `}
              render={data => (
                  <div className="column-2 w-col w-col-4 w-col-stack" key={data.allMarkdownRemark.id}>
                  <div className="text-block"><strong>{data.allMarkdownRemark.frontmatter.title}</strong></div>
                  <div className="text-block-4">{data.allMarkdownRemark.frontmatter.company_role}</div>
                  <div className="text-block-4">{data.allMarkdownRemark.frontmatter.location}</div>
                  <div className="text-block-3">{data.allMarkdownRemark.frontmatter.work_from} – {data.allMarkdownRemark.frontmatter.work_to}</div>
                  <p className="paragraph">{data.allMarkdownRemark.frontmatter.excerpt}</p>
                  <div className="skill-div">{data.allMarkdownRemark.frontmatter.tags}</div>
                  </div>
              )}
              />
              </div>
          </div>
      </div>
);

I get this error TypeError: Cannot read property 'title' of undefined

So what I'm trying to accomplish is this instance across this section. Of course this is a placeholder but I'm looking to replace that placeholder with the contents of each markdown. Experience snip


Update: 2/7/2019

So no changes today but wanted to post a few fields to get a better perspective of what I'm trying to do. This is the config.yml file from NetlifyCMS where it is displaying the collections. This is what I'm accomplishing (Note: the test repo is just to see the actual CMS, I will look to change):

backend:
  name: test-repo
  branch: master

media_folder: static/images
public_folder: /images

display_url: https://gatsby-netlify-cms-example.netlify.com/

# This line should *not* be indented
publish_mode: editorial_workflow

collections:

  - name: "experience"
    label: "Experience"
    folder: "experience"
    create: true
    fields:
        - { name: "title", label: "Company Title", widget: "string" }
        - { name: "company_role", label: "Position Title", widget: "string" }
        - { name: "location", label: "Location", widget: "string" }
        - { name: "work_from", label: "From", widget: "date", format: "MMM YYYY" }
        - { name: "work_to", label: "To", default: "Present", widget: "date", format: "MMM YYYY" }
        - { name: "description", label: "Description", widget: "text" }
        - { name: "tags", label: "Skills Tags", widget: "select", multiple: "true", 
              options: ["ReactJS", "NodeJS", "HTML", "CSS", "Sass", "PHP", "Typescript", "Joomla", "CMS Made Simple"] }


  - name: "blog"
    label: "Blog"
    folder: "blog"
    create: true
    slug: "{{year}}-{{month}}-{{day}}_{{slug}}"
    fields:
      - { name: path, label: Path }
      - { label: "Image", name: "image", widget: "image" }
      - { name: title, label: Title }
      - { label: "Publish Date", name: "date", widget: "datetime" }
      - {label: "Category", name: "category", widget: "string"}
      - { name: "body", label: "body", widget: markdown }
      - { name: tags, label: Tags, widget: list }


  - name: "projects"
    label: "Projects"
    folder: "projects"
    create: true
    fields:
      - { name: date, label: Date, widget: date }
      - {label: "Category", name: "category", widget: "string"}
      - { name: title, label: Title }
      - { label: "Image", name: "image", widget: "image" }
      - { label: "Description", name: "description", widget: "text" }
      - { name: body, label: "Details", widget: markdown }
      - { name: tags, label: Tags, widget: list}


  - name: "about"
    label: "About"
    folder: "src/pages/about"
    create: false
    slug: "{{slug}}"
    fields:
      - {
          label: "Content Type",
          name: "contentType",
          widget: "hidden",
          default: "about",
        }
      - { label: "Path", name: "path", widget: "hidden", default: "/about" }
      - { label: "Title", name: "title", widget: "string" }
      - { label: "Body", name: "body", widget: "markdown" }

And for an example of a markdown page, this would be the format to look for in the Experience section, because as you see in the picture it displays across the container:

---
title: Test Company
company_role: Test Role
location: Anytown, USA
work_from: January, 2020
work_to: January, 2020
tags: Test, Customer Service
---

Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.

Update: 2/8/2019

I do have some updates with the code provided below but before I get into it, here are a few images of what I'm looking to accomplish. These are placeholders that I'm looking to replace for real data. This is for each section:

Full Experience snip

Projects snip

Blog snip

I've ran the code that was provided by @staypuftman in the answer below and came up with this error:

Your site's "gatsby-node.js" created a page with a component that doesn't exist.

I've added the code in addition to what was already there and it processed that error. This is what I originally thought would happen and the reason I wanted to use StaticQuery independently. This was actually the main issue I had with the documentation and the starter repos, no one really has created multiple variables in node.js.

I also tried the revision from @DerekNguyen which looked like this:

import React from "react"
import { Link, graphql, StaticQuery } from "gatsby"



export default(data) => (

        <div id="experience" className="section accent">
                  <div className="w-container">
                      <div className="section-title-group">
                        <Link to="#experience"><h2 className="section-heading centered">Experience</h2></Link>
                      </div>
                  <div className="columns w-row">
                  <StaticQuery
                  query={graphql`
                      query ExperienceQuery {
                          allMarkdownRemark(
                           limit: 2,
                           sort: { order: DESC, fields: [frontmatter___date]},
                           filter: {fileAbsolutePath: {regex: "/(experience)/"}}
                         ) {
                             edges {
                               node {
                                 id
                                 frontmatter {
                                   title
                                   company_role
                                   location
                                   work_from
                                   work_to
                                   tags
                                 }
                                 excerpt
                               }
                             }
                           }
                       }

                  `}
                  render={data.allMarkdownRemark.edges.map(({ node }) => (
                      <div className="column-2 w-col w-col-4 w-col-stack" key={node.id}>
                      <div className="text-block"><strong>{node.frontmatter.title}</strong></div>
                      <div className="text-block-4">{node.frontmatter.company_role}</div>
                      <div className="text-block-4">{node.frontmatter.location}</div>
                      <div className="text-block-3">{node.frontmatter.work_from} – {node.frontmatter.work_to}</div>
                      <p className="paragraph">{node.frontmatter.excerpt}</p>
                      <div className="skill-div">{node.frontmatter.tags}</div>
                      </div>
                  ))}
                  />
                  </div>
              </div>
          </div>
);

However that also came with an error as well:

TypeError: Cannot read property 'edges' of undefined

Still working on it, but I think it is getting closer to the solution. Keep in mind I also would have to create it for the other variables.


Update: 2/10/2019

For those who want to see how I constructed the site using gatsby-starter, here it is below:

My portfolio

Upvotes: 2

Views: 2362

Answers (1)

serraosays
serraosays

Reputation: 7849

gastby-node.js is used when you have a bunch of pages that need to be at /pages/{variable-here}/. Gatsby uses gatsby-node.js to run a GraphQL query against your data source (Netlify CMS in this case) and grabs all the content needed based on your particular GraphQL query.

It then dynamically builds out X number of pages using a component in your project. How many pages it builds depends on what it finds at the remote data source. How they look depends on the component you specify. Read more about this in the Gatsby tutorial.

Staticquery is used to get one-time data into components, not to generate pages from a data source. It is highly useful but not what I think you're trying to do. Read more about it on the Gatsby site.

Based on all of this and what you've provided above, I think your gatsby-node.js should look this:

// Give Node access to path
const path = require('path')

// Leverages node's createPages capabilities
exports.createPages = async ({ graphql, actions }) => {

  // Destructures createPage from redux actions, you'll use this in a minute
  const { createPage } = actions

  // Make your query
  const allExperiencePages = await graphql(`
    { 
      allMarkdownRemark(limit: 1000) {
        edges {
          node {
            id
            frontmatter {
              title
              company_role
              location
              work_from
              work_to
              tags
            }
            excerpt
          }  
        }
      }
    }
  `)

  // gatsby structures allExperiencePages into an object you can loop through
  // The documentation isn't great but the 'data' property contains what you're looking for
  // Run a forEach with a callback parameter that contains each page's data
  allExperiencePages.data.allMarkdownRemark.edges.forEach( page => {

    // Make individual pages with createPage variable you made earlier
    // The 'path' needs to match where you want the pages to be when your site builds
    // 'conponent' is what Gatsby will use to build the page
    // 'context' is the data that the component will receive when you `gatsby build`
    createPage({
      path: `/pages/${page.node.title}/`,
      component: path.resolve('src/components/Experience'),
      context: {
        id: page.node.id,
        title: page.node.frontmatter.title,
        company_role: page.node.frontmatter.company_role,
        location: page.node.frontmatter.location,
        work_from: page.node.frontmatter.work_from,
        work_to: page.node.frontmatter.work_to,
        tags: page.node.frontmatter.tags,
        excerpt: page.node.excerpt
      }
    })
  }) 

}

This alone may not be enough to generate a page! It all depends on what's going on with the component you specify in the createPage component part of the gatsby-node.js file.

Upvotes: 1

Related Questions