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,
},
});
});
};
`
gatsby-config.js: N/A
package.json: N/A
gatsby-node.js: N/A
gatsby-browser.js: N/A
gatsby-ssr.js: N/A
@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 theuseStaticQueryhook. 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" ingatsby-node.js, and depending on the content and it's size inject it through Gatsby's special propcontextand 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
poststo 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
createPageshook
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.
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.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:
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.
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.
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.
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:
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.
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....
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