Gatsby: Graphql Query to extract post matching the current post category

Created on 21 Jan 2020  路  17Comments  路  Source: gatsbyjs/gatsby

I want to display related posts on each individual post page based on the current post category, below is my RecomendedPosts and gatsby-node file code. Currently I have hardcoded the category, how can I make it dynamic, please suggest.

`import React from 'react';
import { useStaticQuery, graphql, Link } from 'gatsby';
import Img from 'gatsby-image';
import '../templates/styles/articleStyles.css';

const RecomendedPosts = () => {

    const data = useStaticQuery(graphql`
    query  {
        allWordpressPost(filter: {categories: {elemMatch: {name: {eq: "General" }}}}, sort: { fields: [date], order: DESC }) {
          edges {
            node {
              title
              excerpt
              slug
              categories {
                id
                name
                slug
              }
              featured_media {
                source_url
                localFile {
                  relativePath 
                  childImageSharp {
                    resolutions(width: 350, height: 200) {
                      ...GatsbyImageSharpResolutions_withWebp
                      src
                      width
                      height
                    }
                  }
                }
             }
            }
          }
        }
      }
    `);

    return (
       <div className="container">
          <div className="row">
            <div className="col-sm-4 pr-sm-2" style={{ paddingRight: '40px' }} >

              {data.allWordpressPost.edges[0].node.featured_media.localFile.childImageSharp.resolutions  != null &&  
            <div className="img-responsive" style={{ width: '244px', height: '200px'}}>
            <Link to={`/blogs/${data.allWordpressPost.edges[0].node.slug}/`} style={{ paddingTop: '50px' }} >
            <Img resolutions={data.allWordpressPost.edges[0].node.featured_media.localFile.childImageSharp.resolutions} style={{ paddingRight: '40px' }}/>  
            </Link>
            </div>
            }


              <Link to={`/blogs/${data.allWordpressPost.edges[0].node.slug}/`} style={{ paddingTop: '50px' }} >
              <b>{data.allWordpressPost.edges[0].node.title}</b>
              </Link>
              </div>

            <div className="col-sm-4 pl-sm-2" style={{ paddingRight: '40px' }}>
            {data.allWordpressPost.edges[11].node.featured_media.localFile.childImageSharp.resolutions  != null &&  
            <div className="img-responsive" style={{ width: '244px', height: '200px'}}>
            <Link to={`/blogs/${data.allWordpressPost.edges[11].node.slug}/`}>
            <Img resolutions={data.allWordpressPost.edges[11].node.featured_media.localFile.childImageSharp.resolutions} />  
            </Link>
            </div>
            }
              <Link to={`/blogs/${data.allWordpressPost.edges[11].node.slug}/`}>
              <b>{data.allWordpressPost.edges[11].node.title}</b>
              </Link>
            </div>
            <div className="col-sm-4 pr-sm-2" style={{ paddingRight: '40px' }}>
            {data.allWordpressPost.edges[2].node.featured_media.localFile.childImageSharp.resolutions  != null &&  
            <div className="img-responsive" style={{ width: '244px', height: '200px'}}>
            <Link to={`/blogs/${data.allWordpressPost.edges[2].node.slug}/`}>
            <Img resolutions={data.allWordpressPost.edges[2].node.featured_media.localFile.childImageSharp.resolutions} />  
            </Link>
            </div>
            }
              <Link to={`/blogs/${data.allWordpressPost.edges[2].node.slug}/`}>
              <b>{data.allWordpressPost.edges[2].node.title}</b>
              </Link>
            </div>

          </div>

          <div className="row">
            <div className="col-sm-4 pr-sm-2" style={{ paddingRight: '40px' }} >

              {data.allWordpressPost.edges[8].node.featured_media.localFile.childImageSharp.resolutions  != null &&  
            <div className="img-responsive" style={{ width: '244px', height: '200px'}}>
            <Link to={`/blogs/${data.allWordpressPost.edges[8].node.slug}/`}  >
            <Img resolutions={data.allWordpressPost.edges[8].node.featured_media.localFile.childImageSharp.resolutions} />  
            </Link>
            </div>
            }


            <Link to={`/blogs/${data.allWordpressPost.edges[8].node.slug}/`}  >
              <b>{data.allWordpressPost.edges[8].node.title}</b>
              </Link>
              </div>

            <div className="col-sm-4 pl-sm-2" style={{ paddingRight: '40px' }}>
            {data.allWordpressPost.edges[5].node.featured_media.localFile.childImageSharp.resolutions  != null &&  
            <div className="img-responsive" style={{ width: '244px', height: '200px'}}>
           <Link to={`/blogs/${data.allWordpressPost.edges[5].node.slug}/`}>
            <Img resolutions={data.allWordpressPost.edges[5].node.featured_media.localFile.childImageSharp.resolutions} />  
            </Link> 
            </div>
            }
              <Link to={`/blogs/${data.allWordpressPost.edges[5].node.slug}/`}>
              <b>{data.allWordpressPost.edges[5].node.title}</b>
              </Link>
            </div>
            <div className="col-sm-4 pr-sm-2" style={{ paddingRight: '40px' }}>
            {data.allWordpressPost.edges[6].node.featured_media.localFile.childImageSharp.resolutions  != null &&  
            <div className="img-responsive" style={{ width: '244px', height: '200px'}}>
            <Link to={`/blogs/${data.allWordpressPost.edges[6].node.slug}/`}>
                <Img resolutions={data.allWordpressPost.edges[6].node.featured_media.localFile.childImageSharp.resolutions} />  
                </Link>
                </div>
            }
              <Link to={`/blogs/${data.allWordpressPost.edges[6].node.slug}/`}>
              <b>{data.allWordpressPost.edges[6].node.title}</b>
              </Link>
            </div>

          </div>

        </div>
      );
  };

  export default RecomendedPosts;`
`const path = require('path');
const slash = require('slash');
const _ = require('lodash');
const { paginate } = require('gatsby-awesome-pagination');

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

  const pageTemplate = path.resolve('./src/templates/page.js');
  const archiveTemplate = path.resolve('./src/templates/archive.js');
  const postTemplate = path.resolve('./src/templates/post.js');

  const result = await graphql(`
    {
      allWordpressPage {
        edges {
          node {
            id
            status
            link
            wordpress_id
            wordpress_parent
          }
        }
      }
      allWordpressPost(filter: {status: {eq: "publish"}}, sort: {order: DESC, fields: date}, limit: 1000 ) {
        edges {
          node {
            id
            link
            status
            categories {
              id
              name
              slug
            }
            featured_media {
              localFile{
                  childImageSharp {
                      id
                  } 
              }
          }   
          }
        }
      }
      allWordpressCategory {
        edges {
          node {
            id
            name
            slug
            count
          }
        }
      }

      allSite {
        edges {
          node {
            siteMetadata {
              title
              description
              author
            }
          }
        }
      }
    site {
       siteMetadata {
         domain: siteUrl
        }
      }  
     }
  `);

  // Check for errors
  if (result.errors) {
    throw new Error(result.errors);
  }

  const {
    allWordpressPage,
    allWordpressPost,
    allWordpressCategory,
  } = result.data;

  exports.onCreateWebpackConfig = ({ actions }) => {
    actions.setWebpackConfig({
        devtool: "eval-source-map"
    });
};


  // Create archive pages for each category
  allWordpressCategory.edges.forEach(catEdge => {
    // First filter out the posts that belongs to the current category
    const filteredPosts = allWordpressPost.edges.filter(
      ({ node: { categories } }) =>
        categories.some(el => el.id === catEdge.node.id)
    );
    // Some categories may be empty and we don't want to show them
    if (filteredPosts.length > 0) {
      paginate({
        createPage,
        items: filteredPosts,
        itemsPerPage: 10,
        pathPrefix: 
        catEdge.node.slug === "blogs"
        ? "/blogs"
        : `/blogs/${catEdge.node.slug}`,
        component: slash(archiveTemplate),
        context: {
          catId: catEdge.node.id,
          catName: catEdge.node.name,
          catSlug: catEdge.node.slug,
          catCount: catEdge.node.count,
          categories: allWordpressCategory.edges,
        },
      });
    }
  });

  allWordpressPage.edges.forEach(edge => {
    if (edge.node.status === 'publish') {
      createPage({
        path: edge.node.link,
        component: slash(pageTemplate),
        context: {
          id: edge.node.id,
          parent: edge.node.wordpress_parent,
          wpId: edge.node.wordpress_id,
        },
      });
    }
  });

  /*const {posts} = result.data.allWordpressPost.edges*/

  _.each(result.data.allWordpressPost.edges, edge =>{
    createPage({
      path: `/blogs${edge.node.link}`,
      component: slash(postTemplate),
      context: {
        id: edge.node.id,
        },
    });
  });
};
`

Summary

Relevant information

Environment (if relevant)

File contents (if changed)

gatsby-config.js: N/A
package.json: N/A
gatsby-node.js: N/A
gatsby-browser.js: N/A
gatsby-ssr.js: N/A

awaiting author response question or discussion

All 17 comments

@expatguideturkey taking a look at your code, as of the current state of Gatsby and to the best of my knowledge, you can't still use variables in static queries, through either the <StaticQuery> element or through the the useStaticQuery hook. More on that here, here and here. Off the top of my head you could turn the GraphQL query into a page query and with that you can inject the variables as needed. Or if you're up for it, you could simplify it even further, by doing the "heavy lifting" in gatsby-node.js, and depending on the content and it's size inject it through Gatsby's special prop context and with that reducing not only the code in the template, but also making it into a presentational component.

@expatguideturkey taking a look at your code, as of the current state of Gatsby and to the best of my knowledge, you can't still use variables in static queries, through either the <StaticQuery> element or through the the useStaticQuery hook. More on that here, here and here. Off the top of my head you could turn the GraphQL query into a page query and with that you can inject the variables as needed. Or if you're up for it, you could simplify it even further, by doing the "heavy lifting" in gatsby-node.js, and depending on the content and it's size inject it through Gatsby's special prop context and with that reducing not only the code in the template, but also making it into a presentational component.

Thanks @jonniebigodes for your response. I have updated my files as below to use pagecontext but its giving me error, please check what I am doing wrong

Error: TypeError: Cannot read property 'posts' of undefined

RecomendedPosts.js

/* eslint-disable react/no-danger */
import React from 'react';
import { graphql, Link } from 'gatsby';
import Img from 'gatsby-image';
import '../templates/styles/articleStyles.css';


const RecomendedPosts = ({
  data: { posts  },
  pageContext: {
    catId,
  },
}) => (

       <div className="container">
          <div className="row" style={{ paddingBottom: '40px' }}>
            <div className="col-sm-4 pl-sm-1" style={{ paddingRight: '40px' }} >

              {posts.edges[0].node.featured_media.localFile.childImageSharp.resolutions  != null &&  
            <div className="img-responsive" style={{ width: '244px', height: '200px'}}>
            <Link to={`/blogs/${posts.edges[0].node.slug}/`} style={{ paddingTop: '50px' }} >
            <Img resolutions={posts.edges[0].node.featured_media.localFile.childImageSharp.resolutions} style={{ paddingRight: '40px' }}/>  
            </Link>
            </div>
            }


              <Link to={`/blogs/${posts.edges[0].node.slug}/`} style={{ paddingTop: '50px' }} >
              <b>{posts.edges[0].node.title}</b>
              </Link>
              </div>

            <div className="col-sm-4 pl-sm-2" style={{ paddingLeft: '40px' }}>
            {posts.edges[11].node.featured_media.localFile.childImageSharp.resolutions  != null &&  
            <div className="img-responsive" style={{ width: '244px', height: '200px'}}>
            <Link to={`/blogs/${posts.edges[11].node.slug}/`}>
            <Img resolutions={posts.edges[11].node.featured_media.localFile.childImageSharp.resolutions} />  
            </Link>
            </div>
            }
              <Link to={`/blogs/${posts.edges[11].node.slug}/`}>
              <b>{posts.edges[11].node.title}</b>
              </Link>
            </div>
            <div className="col-sm-4 pr-sm-2" style={{ paddingRight: '40px' }}>
            {posts.edges[2].node.featured_media.localFile.childImageSharp.resolutions  != null &&  
            <div className="img-responsive" style={{ width: '244px', height: '200px'}}>
            <Link to={`/blogs/${posts.edges[2].node.slug}/`}>
            <Img resolutions={posts.edges[2].node.featured_media.localFile.childImageSharp.resolutions} />  
            </Link>
            </div>
            }
              <Link to={`/blogs/${posts.edges[2].node.slug}/`}>
              <b>{posts.edges[2].node.title}</b>
              </Link>
            </div>

          </div>

          <div className="row">
            <div className="col-sm-4 pl-sm-1" style={{ paddingRight: '40px' }} >

              {posts.edges[8].node.featured_media.localFile.childImageSharp.resolutions  != null &&  
            <div className="img-responsive" style={{ width: '244px', height: '200px'}}>
            <Link to={`/blogs/${posts.edges[8].node.slug}/`}  >
            <Img resolutions={posts.edges[8].node.featured_media.localFile.childImageSharp.resolutions} />  
            </Link>
            </div>
            }


            <Link to={`/blogs/${posts.edges[8].node.slug}/`}  >
              <b>{posts.edges[8].node.title}</b>
              </Link>
              </div>

            <div className="col-sm-4 pl-sm-2" style={{ paddingRight: '40px' }}>
            {posts.edges[5].node.featured_media.localFile.childImageSharp.resolutions  != null &&  
            <div className="img-responsive" style={{ width: '244px', height: '200px'}}>
           <Link to={`/blogs/${posts.edges[5].node.slug}/`}>
            <Img resolutions={posts.edges[5].node.featured_media.localFile.childImageSharp.resolutions} />  
            </Link> 
            </div>
            }
              <Link to={`/blogs/${posts.edges[5].node.slug}/`}>
              <b>{posts.edges[5].node.title}</b>
              </Link>
            </div>
            <div className="col-sm-4 pr-sm-2" style={{ paddingRight: '40px' }}>
            {posts.edges[6].node.featured_media.localFile.childImageSharp.resolutions  != null &&  
            <div className="img-responsive" style={{ width: '244px', height: '200px'}}>
            <Link to={`/blogs/${posts.edges[6].node.slug}/`}>
                <Img resolutions={posts.edges[6].node.featured_media.localFile.childImageSharp.resolutions} />  
                </Link>
                </div>
            }
              <Link to={`/blogs/${posts.edges[6].node.slug}/`}>
              <b>{posts.edges[6].node.title}</b>
              </Link>
            </div>

          </div>

        </div>
      );
  export default RecomendedPosts;


  export const pageQuery = graphql`
  query($catId: String!, $skip: Int!, $limit: Int!) {
    allWordpressPost(
      filter: { categories: { elemMatch: { id: { eq: $catId } } } }
      skip: $skip
      limit: $limit
    ) {
      edges {
        node {
          id
          title
          excerpt
          slug
          date(formatString: "DD, MMM, YYYY")
          featured_media {
            source_url
            localFile {
              relativePath 
              childImageSharp {
                resolutions(width: 244, height: 200) {
                  ...GatsbyImageSharpResolutions_withWebp
                  src
                  width
                  height
                }
              }
            }
         }
      }
    }
  }
    file(relativePath: { eq: "archive_expat_headerImage.jpg" }) {
      childImageSharp {
        fluid(quality: 100, maxWidth: 1250) {
          ...GatsbyImageSharpFluid_withWebp
        }
      }
    }
  }
`;

gatsby-node.js

`const path = require('path');
const slash = require('slash');
const _ = require('lodash');
const { paginate } = require('gatsby-awesome-pagination');

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

  const pageTemplate = path.resolve('./src/templates/page.js');
  const archiveTemplate = path.resolve('./src/templates/archive.js');
  const postTemplate = path.resolve('./src/templates/post.js');

  const result = await graphql(`
    {
      allWordpressPage {
        edges {
          node {
            id
            status
            link
            wordpress_id
            wordpress_parent
          }
        }
      }
      allWordpressPost(filter: {status: {eq: "publish"}}, sort: {order: DESC, fields: date}, limit: 1000 ) {
        edges {
          node {
            id
            link
            status
            categories {
              id
              name
              slug
            }
            featured_media {
              localFile{
                  childImageSharp {
                      id
                  } 
              }
          }   
          }
        }
      }
      allWordpressCategory {
        edges {
          node {
            id
            name
            slug
            count
          }
        }
      }

      allSite {
        edges {
          node {
            siteMetadata {
              title
              description
              author
            }
          }
        }
      }
    site {
       siteMetadata {
         domain: siteUrl
        }
      }  
     }
  `);

  // Check for errors
  if (result.errors) {
    throw new Error(result.errors);
  }

  const {
    allWordpressPage,
    allWordpressPost,
    allWordpressCategory,
  } = result.data;

  exports.onCreateWebpackConfig = ({ actions }) => {
    actions.setWebpackConfig({
        devtool: "eval-source-map"
    });
};


  // Create archive pages for each category
  allWordpressCategory.edges.forEach(catEdge => {
    // First filter out the posts that belongs to the current category
    const filteredPosts = allWordpressPost.edges.filter(
      ({ node: { categories } }) =>
        categories.some(el => el.id === catEdge.node.id)
    );
    // Some categories may be empty and we don't want to show them
    if (filteredPosts.length > 0) {
      paginate({
        createPage,
        items: filteredPosts,
        itemsPerPage: 10,
        pathPrefix: 
        catEdge.node.slug === "blogs"
        ? "/blogs"
        : `/blogs/${catEdge.node.slug}`,
        component: slash(archiveTemplate),
        context: {
          catId: catEdge.node.id,
          catName: catEdge.node.name,
          catSlug: catEdge.node.slug,
          catCount: catEdge.node.count,
          categories: allWordpressCategory.edges,
        },
      });
    }
  });

  allWordpressPage.edges.forEach(edge => {
    if (edge.node.status === 'publish') {
      createPage({
        path: edge.node.link,
        component: slash(pageTemplate),
        context: {
          id: edge.node.id,
          parent: edge.node.wordpress_parent,
          wpId: edge.node.wordpress_id,
        },
      });
    }
  });

  /*const {posts} = result.data.allWordpressPost.edges*/

  _.each(result.data.allWordpressPost.edges, edge =>{
    createPage({
      path: `/blogs${edge.node.link}`,
      component: slash(postTemplate),
      context: {
        id: edge.node.id,
        posts:allWordpressPost.edges,
        catId: edge.node.categories.id,
        },
    });
  });
};
`

Error occurs here (see data: { posts } destructuring):

const RecomendedPosts = ({
  data: { posts  },
  pageContext: {
    catId,
  },
})

Since you've moved posts to context, it should be:

const RecomendedPosts = ({
  pageContext: {
    catId,
    posts,
  },
})

@expatguideturkey also one more thing you need to take care of

exports.onCreateWebpackConfig = ({ actions }) => {
    actions.setWebpackConfig({
        devtool: "eval-source-map"
    });
};

The code above is it's own api hook, it should be outside the createPages hook

Error occurs here (see data: { posts } destructuring):

const RecomendedPosts = ({
  data: { posts  },
  pageContext: {
    catId,
  },
})

Since you've moved posts to context, it should be:

const RecomendedPosts = ({
  pageContext: {
    catId,
    posts,
  },
})

Thanks a lot @vladar for pointing out. It doesn't show any error on page but the page is blank, not showing any content not even the current post. However, during debug it shows: Uncaught TypeError: Cannot read property 'posts' of undefined and Uncaught TypeError: Cannot read property 'catId' of undefined

Below is my post.js where I am calling RecomendedPosts component. Any suggestions:

Post.js

import React, { Component } from 'react';
import { graphql } from 'gatsby';
import PropTypes from 'prop-types';
import styled from 'styled-components';

import Layout from '../components/layout';
import Breadcrumb from '../components/BreadCrumb';
import PostSidebar from '../components/post/PostSidebar';
import PageHero from '../components/PageHero';
import PageTransition from 'gatsby-plugin-page-transitions';
import ShareButtons from "../components/ShareButtons";
import TalkyardCommentsIframe from '@debiki/gatsby-plugin-talkyard';
import RecomendedPosts from '../components/RecomendedPosts';

const PostContent = styled.article`
  margin: 20px 0 0 0;
`;

 class postTemplate extends React.Component{

  render(){

 const post = this.props.data.post


 return(  

<PageTransition>
<Layout>
    <PageHero img={post.featured_media.localFile.childImageSharp.fluid} />

<Breadcrumb
      parent={{
        link: '/blogs/all-blogs',
        title: 'blogs',
      }}
    />
    <div className="container">
      <div className="row" style={{ marginBottom: '40px' }}>
        <PostSidebar
          date={post.date}
          author={post.author.name}
          categories={post.categories}
        />
        <PostContent className="col-lg-9">
          <h1 dangerouslySetInnerHTML={{ __html: post.title }} />
          <div dangerouslySetInnerHTML={{ __html: post.content }} />


        </PostContent>
         <ShareButtons/>
         <div className="col-lg-12"> <TalkyardCommentsIframe /></div>
        <h2 className="section-title separator-below"> Related Post - </h2><p>Here are a couple of related posts you might enjoy reading.</p>
        <RecomendedPosts />
        </div>
    </div>

    </Layout>
  </PageTransition>
) 
}
 }

export default postTemplate;

export const pageQuery = graphql`
  query($id: String!) {
    post: wordpressPost(id: { eq: $id }) {
      id 
      title
      content
      excerpt
      slug
      author {
        name
      }
      date(formatString: "DD, MMM, YYYY")
      categories {
        id
        name
        slug
      }

      featured_media {
        source_url
        localFile {
          relativePath 
          childImageSharp {
            fluid(quality: 100, maxWidth: 900) {
              ...GatsbyImageSharpFluid_withWebp
            src
            }
          }
        }
      }
    }
    }
  `;

@expatguideturkey also one more thing you need to take care of

exports.onCreateWebpackConfig = ({ actions }) => {
    actions.setWebpackConfig({
        devtool: "eval-source-map"
    });
};

The code above is it's own api hook, it should be outside the createPages hook

Thanks a lot @jonniebigodes for pointing out my mistake. I moved it outside of the 'createPages' hook.

@jonniebigodes @vladar Any suggestion for my updated code.

@expatguideturkey can you create a reproduction following these steps with all the packages used and the code for this issue, so that it can be better looked at?

@expatguideturkey can you create a reproduction following these steps with all the packages used and the code for this issue, so that it can be better looked at?

thank you @jonniebigodes for your response. here is the Repro link: https://github.com/expatguideturkey/expat-repro.git
if you browse blogs and then to individual blog post you get the error: TypeError: Cannot read property 'catId' of undefined

@expatguideturkey i'm cloning the repo as i type this. I'm going to take a look at it and come back with a possible solution for your issue. Do you mind waiting a bit while i take a closer look at this?

@expatguideturkey i'm cloning the repo as i type this. I'm going to take a look at it and come back with a possible solution for your issue. Do you mind waiting a bit while i take a closer look at this?

@jonniebigodes yes its ok, I can wait, I want to be solved this issue.

Hi @jonniebigodes how is it going, have you found the solution to fix this issue.

@expatguideturkey i've cloned your repo and i think i have a solution for your issue. I'm going to detail it below, and with that give you two options on how you could proceed.

  • Like i said, cloned the repo.
  • Installed the dependencies.
  • Checked the template src\templates\post.js and the component src\components\RecomendedPost.js and part of the issue lies there, @vladar mislead you inadvertly, by sugesting the code change, as when i read the description he assumed that the RecomendedPosts.js was actually a page component/template, so that's why he sugested you use it.
  • Also i checked the gatsby-node.js file and you the change i mentioned was still not applied, probably you did not commit it yet.

Now for the options that i've mentioned.

In this case you can go about it by doing the following:

  • Change your gatsby-node.js file to the following (i'm leaving out the graphql and "imports" for brevity purposes

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

  const pageTemplate = path.resolve("./src/templates/page.js")
  const archiveTemplate = path.resolve("./src/templates/archive.js")
  const postTemplate = path.resolve("./src/templates/post.js")

  // Check for errors
  if (result.errors) {
    throw new Error(result.errors)
  }

  const {
    allWordpressPage,
    allWordpressPost,
    allWordpressCategory,
  } = result.data

  // Create archive pages for each category
  allWordpressCategory.edges.forEach(catEdge => {
    // First filter out the posts that belongs to the current category
    const filteredPosts = allWordpressPost.edges.filter(
      ({ node: { categories } }) =>
        categories.some(el => el.id === catEdge.node.id)
    )
    // Some categories may be empty and we don't want to show them
    if (filteredPosts.length > 0) {
      paginate({
        createPage,
        items: filteredPosts,
        itemsPerPage: 10,
        pathPrefix:
          catEdge.node.slug === "blogs"
            ? "/blogs"
            : `/blogs/${catEdge.node.slug}`,
        component: slash(archiveTemplate),
        context: {
          catId: catEdge.node.id,
          catName: catEdge.node.name,
          catSlug: catEdge.node.slug,
          catCount: catEdge.node.count,
          categories: allWordpressCategory.edges,
        },
      })
    }
  })

  allWordpressPage.edges.forEach(edge => {
    if (edge.node.status === "publish") {
      createPage({
        path: edge.node.link,
        component: slash(pageTemplate),
        context: {
          id: edge.node.id,
          parent: edge.node.wordpress_parent,
          wpId: edge.node.wordpress_id,
        },
      })
    }
  })

  /*const {posts} = result.data.allWordpressPost.edges*/

  _.each(result.data.allWordpressPost.edges, edge => {
    createPage({
      path: `/blogs${edge.node.link}`,
      component: slash(postTemplate),
      context: {
        id: edge.node.id,
        //catId: edge.node.categories.id, // <-- this is an array and you're trying to access a single item to it and it's not not picked up and injected.
        categoryList:edge.node.categories.map(category=>category.id) // adds a category array to be used in the graphql query
      },
    })
  })
}

exports.onCreateWebpackConfig = ({ actions }) => {
  actions.setWebpackConfig({
    devtool: "eval-source-map",
  })
}

As you can see the webpack config api hook is in it's correct place and also if you check the comments i left in, you'll see that you trying to add a element to Gatsby special prop context that is not correct, you're trying to add catId as a single item, when what is actually returned from the query is a array. This might work while you're developing the app/site with Gatsby develop, as Gatsby is way more permissive and let's you "get away with it" (pardon the bad pun) but things might become more interesting when you switch to production. Getting build errors and with that you'll waste time tracking them down. Also you might have noticed the introduction of categoryList, this element will be injected into page, it will grab each category id that is associated to the page and it will used as a graphql query variable, you'll see that shortly.

  • In the template file, more specifically src\templates\post.js i made a couple changes to it and it now looks like the following:
/* eslint-disable react/no-danger */
import React from "react"
import { graphql } from "gatsby"
import SEO from "../components/seo"
import styled from "styled-components"
import Layout from "../components/layout"

import RecomendedPosts from "../components/RecomendedPosts"

const PostContent = styled.article`
  margin: 20px 0 0 0;
`

class postTemplate extends React.Component {
  render() {
    const post = this.props.data.post
    return (
      <Layout>
        <SEO
          title={post.title}
          description={post.excerpt}
          keywords={["expat", "guide", "turkey"]}
        />
        <div className="container">
          <div className="row" style={{ marginBottom: "40px" }}>
            <PostContent className="col-lg-9">
              <h1 dangerouslySetInnerHTML={{ __html: post.title }} />
              <div dangerouslySetInnerHTML={{ __html: post.content }} />
            </PostContent>
            <h2 className="section-title separator-below"> Related Post - </h2>
            <p> Here are a couple of related posts you might enjoy reading.</p>
            <RecomendedPosts relatedPosts={this.props.data.relatedPosts}/>
          </div>

        </div>
      </Layout>
    )
  }
}
export default postTemplate

export const pageQuery = graphql`
query ($id: String!, $categoryList: [String!]) {
  post: wordpressPost(id: {eq: $id}) {
    id
    title
    content
    excerpt
    slug
    author {
      name
    }
    date(formatString: "DD, MMM, YYYY")
    categories {
      id
      name
      slug
    }
  }
  relatedPosts: allWordpressPost(filter: {categories: {elemMatch: {id: {in: $categoryList}}}}, limit: 10) {
    edges {
      node {
        id
        title
        excerpt
        slug
        date(formatString: "DD, MMM, YYYY")
        categories {
          id
          name
          slug
        }
      }
    }
  }
}
`

Key things to take from this, the query is changed to fetch not only the post and also the related posts associated with it based on the query result, which is aliased by relatedPosts.

  • Based on the above, i changed the src\components\RecomendedPosts.js to the following:
/* eslint-disable react/no-danger */
import React from "react"
import { Link } from "gatsby"
import "../templates/styles/articleStyles.css"

/**
 *  The component is now a fully functional one, no need for adding extra graphql imports and hooks
 * @param {Object} relatedPosts the destructured object that contains the information about the related posts 
 */
const RecomendedPosts = ({ relatedPosts }) => {
  // checks for null items
  if (!relatedPosts) {
    return (
      <div className="container">
        <div className="col-sm-4 pl-sm-1" style={{ paddingRight: "40px" }}>
          No related items
        </div>
      </div>
    )
  }
  return (
    <div className="container">
      {relatedPosts.edges.map(relatedPost => (
        <div className="col-sm-4 pl-sm-1" style={{ paddingRight: "40px" }}>
          <Link
            to={`/blogs/${relatedPost.node.slug}/`}
            style={{ paddingTop: "50px" }}
          >
            <b>{relatedPost.node.title}</b>
          </Link>
        </div>
      ))}
    </div>
  )
}
export default RecomendedPosts

I left in some comments, but as you can see it's now a fully functional component that "his job" is to render the information available, i left in a "sanity check " just to make sure that it doesn't blow up in case of the relatedPosts prop is null.

  • Issued yarn develop (i'm using yarn, if you use npm adjust accordingly) to generate a development build and i opened up the site and a random blog entry and i'm presented with the following:
    expat_turkey_1

On the left side is the entry with the associated posts, and on the right side one item opened based the one on the left.

Now for the second option.

  • On your gatsby-node.js file, as you already have all the information at your disposal, we can streamline the process of getting the related posts associated to a particular entry. As the information that is required is small, you can do the following change:
/**
 * function to get relatedPosts, it will get them if they have only one category in common
 * @param {Object} currentPost the current element being checked
 * @param {Array} posts the list of edges being processed
 */
function getRelatedPosts(currentPost, posts){

  /**
   * searches for any post that shares at least one category in common
   * @param {Object} node the node being processed
   */
  const hasCommonCategories=({node})=>{
    // sanity check for checking if the same node is being processed
    if (currentPost.node.id===node.id){
      return false;
    }

    // creates a array based on the common item( one category in question) and returns something true
    const commonCategories= _.intersectionBy(currentPost.node.categories,node.categories,(category)=>category.id)
    return commonCategories.length>=1
  }

  // filters the list of posts based on the function above
  const filteredResults= posts.filter(hasCommonCategories)
  //slices the array to return only 5 related items
  if (filteredResults.length>5){
    return filteredResults.slice(0,5)
  }

  return filteredResults
}

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

  const pageTemplate = path.resolve("./src/templates/page.js")
  const archiveTemplate = path.resolve("./src/templates/archive.js")
  const postTemplate = path.resolve("./src/templates/post.js")


  // Create archive pages for each category
  allWordpressCategory.edges.forEach(catEdge => {
    // First filter out the posts that belongs to the current category
    const filteredPosts = allWordpressPost.edges.filter(
      ({ node: { categories } }) =>
        categories.some(el => el.id === catEdge.node.id)
    )
    // Some categories may be empty and we don't want to show them
    if (filteredPosts.length > 0) {
      paginate({
        createPage,
        items: filteredPosts,
        itemsPerPage: 10,
        pathPrefix:
          catEdge.node.slug === "blogs"
            ? "/blogs"
            : `/blogs/${catEdge.node.slug}`,
        component: slash(archiveTemplate),
        context: {
          catId: catEdge.node.id,
          catName: catEdge.node.name,
          catSlug: catEdge.node.slug,
          catCount: catEdge.node.count,
          categories: allWordpressCategory.edges,
        },
      })
    }
  })

  allWordpressPage.edges.forEach(edge => {
    if (edge.node.status === "publish") {
      createPage({
        path: edge.node.link,
        component: slash(pageTemplate),
        context: {
          id: edge.node.id,
          parent: edge.node.wordpress_parent,
          wpId: edge.node.wordpress_id,
        },
      })
    }
  })

  _.each(result.data.allWordpressPost.edges, edge => {
    /* const relatedPostItems = allWordpressPost.edges.filter(relatedPost=> !edge.node.categories.includes(relatedPost.node.categories)) */
    const relatedPostItems = getRelatedPosts(edge,result.data.allWordpressPost.edges) // calls the function above
    createPage({
      path: `/blogs${edge.node.link}`,
      component: slash(postTemplate),
      context: {
        id: edge.node.id,
        /*  posts: allWordpressPost.edges, */
        //catId: edge.node.categories.id, // <-- this is an array and you're trying to access a single item to it and not picked up
        relatedPosts: relatedPostItems,
      },
    })
  })
}

exports.onCreateWebpackConfig = ({ actions }) => {
  actions.setWebpackConfig({
    devtool: "eval-source-map",
  })
}

Key things to take from this, i created a small auxiliary function to filter out the posts that are fetched from your cms, more specifically the function getRelatedPosts and like above i left out the graphql query and imports for brevity purposes. I would like to point out that this is not "battle tested"(pardon the bad pun) and probably you can fine tune this better. Performance wise, i much rather prefer to "take a hit" (once again pardon the bad pun) in here than later. This could be even be more improved, by fetching all the data in gatsby-node.js and injecting it into the template, making it a fully presentational component. But i'll leave that to you to consider.

Moving on....

  • Based on the change above the template src\templates\post.js was changed back to it's original form, with the caveat that the related posts will passed down via props based on what's in pageContext:
import RecomendedPosts from "../components/RecomendedPosts"

const PostContent = styled.article`
  margin: 20px 0 0 0;
`

class postTemplate extends React.Component {
  render() {
    const post = this.props.data.post
    return (
      <Layout>
        <SEO
          title={post.title}
          description={post.excerpt}
          keywords={["expat", "guide", "turkey"]}
        />
        <div className="container">
          <div className="row" style={{ marginBottom: "40px" }}>
            <PostContent className="col-lg-9">
              <h1 dangerouslySetInnerHTML={{ __html: post.title }} />
              <div dangerouslySetInnerHTML={{ __html: post.content }} />
            </PostContent>
            <h2 className="section-title separator-below"> Related Post - </h2>
            <p> Here are a couple of related posts you might enjoy reading.</p>
            <RecomendedPosts relatedPosts={this.props.pageContext.relatedPosts}/>
          </div>
        </div>
      </Layout>
    )
  }
}

export default postTemplate

export const pageQuery = graphql`
query($id: String!) {
  post: wordpressPost(id: { eq: $id }) {
    id
    title
    content
    excerpt
    slug
    author {
      name
    }
    date(formatString: "DD, MMM, YYYY")
    categories {
      id
      name
      slug
    }
  }
}
`

And with this change the src\components\RecomendedPosts.js was changed a bit to the following:

const RecomendedPosts = ({ relatedPosts }) => {
  // checks for null items
  if (relatedPosts.length===0) {
    return (
      <div className="container">
        <div className="col-sm-4 pl-sm-1" style={{ paddingRight: "40px" }}>
          No related items
        </div>
      </div>
    )
  }
  return (
    <div className="container">
      {relatedPosts.map(relatedPost => (
        <div className="col-sm-4 pl-sm-1" style={{ paddingRight: "40px" }} >
          <Link
            to={`/blogs${relatedPost.node.link}`}
            style={{ paddingTop: "50px" }}
          >
            <b>{relatedPost.node.link}</b>
          </Link>
        </div>
      ))}
    </div>
  )
}
export default RecomendedPosts

Key thing to take from this change is the following, right now i'm showing the link, as techically i don't have a title element present in the recomended posts, that's why i'm showing the link instead.

This could be adjusted to show all the information required, by updating the query in gatsby-node.js. I'll leave it up to you on how you wish to procceed.

I'm really sorry for taking to much time answering, but i was sidetracked with some real life items that need to be addressed and ended up taking way too long. Also i would like to appologize for the extreme long post, but i would like to give you the best answer i could so that the issue could be solved.

Feel free to provide feedback so that we can close this issue, or continue to work on it until we find a suitable solution.

Thanks a lot @jonniebigodes for resolving my issue 馃 and such a detailed response. You鈥檙e awesome! After implementing your code, I got the exact results as yours. I am so happy :)

I really appreciate for your support and time. You provided the best and clear solution with all these commented explanations you provided has helped me learn the syntax and concepts which will definitely help me in future.

Once again Thanks a million 馃挴 for explaining all this to me.

@expatguideturkey no need to thank, i was glad that i was able to help you solve your issue. Once again i'm the one that should be sorry as i got sidetracked and was not able to post a response to your issue.

On a side note, a more particular one. I checked the site and his shaping up really good and it's topic is one of great interest. I really hope this gets featured in the showcase in the near future.

Feel free to close this issue as it's solved.

Wow, @jonniebigodes this is an exhaustive answer 馃

@vladar just like every issue i pick up, i tend to give the person the most accurate and most complete answer so that he/she can go through it at his/her own pace and with that learn a bit more about the framework. In this particular i presented a couple of ways this could be fixed

Was this page helpful?
0 / 5 - 0 ratings

Related issues

brandonmp picture brandonmp  路  3Comments

Oppenheimer1 picture Oppenheimer1  路  3Comments

dustinhorton picture dustinhorton  路  3Comments

magicly picture magicly  路  3Comments

theduke picture theduke  路  3Comments