question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Graphql Query to extract post matching the current post category

See original GitHub issue

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

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:17 (2 by maintainers)

github_iconTop GitHub Comments

1reaction
jonniebigodescommented, Feb 5, 2020

@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

1reaction
jonniebigodescommented, Feb 3, 2020

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

Read more comments on GitHub >

github_iconTop Results From Across the Web

Getting the categoryId of a post in Graphql
This is my Graphql query for getting posts from headless wordpress: export const GET_POSTS = gql` query GET_POSTS( $uri: String, ...
Read more >
GraphQL Search and Filter – How to ...
The goal is to find an item (or a list of items) that matches some sort of criteria. For the rest of this...
Read more >
How to query some posts and then get all their categories
I only get the first category listed for each post. Is there a way to get all of them? Thanks for any advice....
Read more >
Search and Filtering - GraphQL
Get a single object; Query a list of objects; Query that filters objects by ... In the query below, we fetch posts that...
Read more >
GraphQL Query Options
In this query the fields categories and title are filtered to find the book that belongs to the magical creatures category AND has...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found