2

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

H.Tibbs
  • 21
  • 4
  • Where is this Experience component located in your src? Could it be that you’re using page query in a non-page component? – Derek Nguyen Feb 06 '19 at 04:10
  • So the experience.js component is in a different directory. That's in src/components/Experience. I have each section of the main page separated into sub-components and they go to their respective directories. And then if I need to add a specific page to that section, it goes to that directory. – H.Tibbs Feb 06 '19 at 06:19
  • Then you won't be able to export graphql query and expect `data` in props, gatsby will ignore your query! You have to use [StaticQuery](https://www.gatsbyjs.org/docs/static-query/) instead – Derek Nguyen Feb 06 '19 at 06:21
  • I have StaticQuery commented out on my actual code. If this works, I thank you greatly @DerekNguyen – H.Tibbs Feb 06 '19 at 09:26
  • It didn't work for me, I have to be missing something. I'll keep the StaticQuery up but there is something missing. – H.Tibbs Feb 06 '19 at 10:49
  • Could you edit the answer to add the static query part? – Derek Nguyen Feb 06 '19 at 10:57
  • Sorry about that, was away from my computer – H.Tibbs Feb 06 '19 at 20:20
  • It's weird to be using `Staticquery` and gatsby-node.js together like this. If you are fetching data with gatsby-node.js you should hand that off the component and not need additional data through `Staticquery`. There might be an architecture problem that better component composition can fix. – serraosays Feb 07 '19 at 14:40
  • You should also post your markdown data model, because the error you are receiving is basically Webpack saying it can't find `title` in your markdown file. – serraosays Feb 07 '19 at 14:41
  • @staypuftman I have yet to connect the NetlifyCMS side, but I could show you what I'm looking for in the collections in config.yml. I'm not sure where the markdown data model is that you are referring to, but if you need the config.yml from Netlify, I could post that. Because of the markdowns that I already have in my directory, each one has a title. I'll post one. – H.Tibbs Feb 07 '19 at 21:10
  • I think there is an error in you query: `fields: [frontmatter___date]` no bracket on the end. You're closing up the allMarkdownRemark when you shouldn't be. – serraosays Feb 07 '19 at 21:26
  • I've also added the config.js file as well. I believe I'm stumped as to how to fetch the data from gatsby-node.js as I felt that once you create the query from graphql, it would be able to display that information. Basically saying I'm not sure where I would define it in node.js. – H.Tibbs Feb 07 '19 at 21:31
  • @staypuftman The query that I have is at the very top and I'll make a quick adjustment here. Make a space between brackets at fields: [frontmatter___date]. – H.Tibbs Feb 07 '19 at 21:40
  • In your render method of StaticQuery, you’re treating `allMarkdownRemark` as if it’s a node. It is not, instead you’d have to map over `allMarkdownRemark.egdes` to get the node, i.e `data.allMarkdownRemark.edges.map(({ node }) => (
    ...
    ))`
    – Derek Nguyen Feb 08 '19 at 04:40
  • I see, kinda how I did it in the beginning block only instead for StaticQuery. I'll check that out. – H.Tibbs Feb 08 '19 at 06:25

1 Answers1

1

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.

serraosays
  • 7,163
  • 3
  • 35
  • 60
  • Thank you very much for this answer. I will try this out on my end. I believe I was avoiding make pages for the experience markdown pages. I thought that you could create instances of the fields that you need in the component without creating a page from that specific file. I will work on this and let you know what I get. – H.Tibbs Feb 07 '19 at 22:11
  • Also and this is what the reason I'm doing this, there will also be a Projects Page and the Blog Page with each section displaying the top 3 most recent instances on the main page. With what you have above, would I create a const for allProjects and allBlogs the same way? – H.Tibbs Feb 07 '19 at 22:16
  • From OP’s question, it looks like he wants to have a independent `Experience` component that can then be placed inside other pages or post templates, not creating separate Experience pages. @H.Tibbs is that right? – Derek Nguyen Feb 08 '19 at 04:53
  • @DerekNguyen Correct. So I need these components to populate new instances: Experiences, Projects, and Blogs. Each will display the top 3 most recent instances (for example my most recent experience, most recent projects, etc.) on the Home page, but when you click on the section text or See More (thinking of adding that Call of Action), it will display an entire list of that component in descending order. So like a blog page listing all blogs and then clicks to each blog post. – H.Tibbs Feb 08 '19 at 06:30
  • Furthermore, Experience entries doesn't have to be an individual post, but for the sake of keeping it simple it could be the same type of markdown. – H.Tibbs Feb 08 '19 at 06:32
  • Can you post so visuals of what this will look like? Are any of the pages dynamically generated? – serraosays Feb 08 '19 at 14:43
  • @staypuftman I'll post a few pics of what I'm trying to do above in the original question. Projects, Experiences, and Blogs will be dynamically generated because each one can be added or deleted. Each section's content will be created and maintained through NetlifyCMS where you can add, edit, or delete them. – H.Tibbs Feb 08 '19 at 19:30
  • But the key question is which PAGES will be dynamically generated. You dynamically generate pages in `gatsby-node.js`. Dynamically generated SECTIONS happen with 'Staticquery` on static pages. – serraosays Feb 08 '19 at 20:17
  • I believe I was trying to dynamically generate sections. So rethinking this, generating pages would have to be done because each page or markdown will have a unique property. So every experience will be different, every project will be different, every blog will be different. Sorry for any confusion. – H.Tibbs Feb 09 '19 at 10:53
  • I think you need a mix of both dynamic pages with dynamically generated sections. So, you'd have the dynamically generated pages, like I've done above, and then inside of the component you reference inside of `createPage`, that's where the staticquery would go. Let me know if that makes sense. – serraosays Feb 10 '19 at 17:29
  • I'll display inside of code sandbox to show you how it is constructed. Maybe that would kinda give a better perspective of what I'm aiming for. But as I've mentioned about node.js, I believe that's where the code needs to be first before it goes out to the components and that's where the difficulty is. – H.Tibbs Feb 10 '19 at 21:09