Displaying all blog posts with Gatsby Contentful - reactjs

Im trying to display all my Contentful blog posts to my index page in Gatsby but i get an error.
im creating the Posts pages on gatsby-node.js like this:
const path = require(`path`)
// Log out information after a build is done
exports.onPostBuild = ({ reporter }) => {
reporter.info(`Your Gatsby site has been built!`)
}
// Create blog pages dynamically
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions
const blogPostTemplate = path.resolve(`src/templates/blogPost.js`)
const result = await graphql(`
query {
allContentfulPost {
edges {
node {
postTitle
slug
}
}
}
}
`)
result.data.allContentfulPost.edges.forEach(edge => {
createPage({
path: `${edge.node.slug}`,
component: blogPostTemplate,
context: {
title: edge.node.postTitle,
slug: edge.node.slug,
},
})
})
}
based on this template:
import React from "react"
import { graphql } from "gatsby"
import styled from "styled-components"
export const pageQuery = graphql`
query($slug: String!) {
post: contentfulPost(slug: { eq: $slug }) {
slug
postTitle
postContent {
childMarkdownRemark {
html
}
}
postImage {
title
fluid {
src
}
}
}
}
`
function blogPost({ data }) {
return (
<div>
<img
src={data.post.postImage.fluid.src}
alt={data.post.postImage.title}
></img>
<h1>{data.post.postTitle}</h1>
<h3
dangerouslySetInnerHTML={{
__html: data.post.postContent.childMarkdownRemark.html,
}}
/>
</div>
)
}
export default blogPost
Now i try to create a component which will hold all the blog posts so i can display it on my index.js page, like this:
import { Link, graphql, StaticQuery } from "gatsby"
import React from "react"
import styled from "styled-components"
function BlogSection() {
return (
<StaticQuery
query={graphql`
query blogQuery {
allContentfulPost {
edges {
node {
slug
postTitle
postImage {
file {
url
fileName
}
}
postContent {
postContent
}
postDate
}
}
}
}
`}
render={data => (
<ul>
<Link to={data.allContentfulPost.edges.node.slug}> //here's where the error happens
{data.allContentfulPost.edges.node.postTitle}
</Link>
</ul>
)}
/>
)
}
export default BlogSection
But i get an error Cannot read property 'slug' of undefined which is driving me crazy for days.
any help would be appreciated!

Use:
<ul>
{data.allContentfulPost.edges.map(({ node }) => {
return <Link to={node.slug} key={node.slug}>
{node.postTitle}
</Link>
})}
</ul>
You are querying all pots from Contentful (allContentfulPost) which following the nested structure, has an edges and a node inside: this last one has all the information of your posts (because of the nested structure, you have the slug, the postTitle, etc) so the node, is indeed your post. That said, you only need to loop through edges, which is an array of your posts. In the previous snippet:
data.allContentfulPost.edges.map(({ node })
You are destructuring the iterable variable at the same time you loop through it ({ node }). You can alias it for a more succint approach like:
<ul>
{data.allContentfulPost.edges.map(({ node: post }) => {
return <Link to={post.slug} key={post.slug}>
{post.postTitle}
</Link>
})}
</ul>
It's important to use the key attribute in all loops since it will help React to know what elements are changing.

Related

Rendering Rich Text from CMS in Gatsby v4

I'm trying to render rich text in my Gatsby v4 about page for my site, but I'm having trouble finding any info on how to render the data. I've read about adding blocks, but I'm lost on what I should be including or how to go about this. I really just need to render links, headers, and body text in the rich text. Could someone walk me through this?
Here's my component code snippet. The data is all coming through the query correctly in the page so far. I just want the text to go where it says "TEXT GOES HERE"
import React from "react"
import { useStaticQuery, graphql } from "gatsby"
import { GatsbyImage } from "gatsby-plugin-image"
import {renderRichText} from "gatsby-source-contentful/rich-text"
import {BLOCKS, MARKS} from "#contentful/rich-text-types"
import * as aboutStyles from "../styles/about.module.scss"
const query = graphql`
{
contentfulAbout {
about
bioImage {
title
url
gatsbyImageData(
layout: FULL_WIDTH
placeholder: BLURRED
resizingBehavior: SCALE
width: 1000
)
}
aboutText {
raw
}
}
}
`
const AboutSection = () => {
const data = useStaticQuery(query);
const contentfulAbout = data.contentfulAbout
return (
<div className={aboutStyles.parent}>
<section className={aboutStyles.container}>
<div className={aboutStyles.image}>
<GatsbyImage className={aboutStyles.bioImage} image={contentfulAbout.bioImage.gatsbyImageData} alt={contentfulAbout.bioImage.title} />
</div>
<div className={aboutStyles.text}>
<h2>{contentfulAbout.about}</h2>
<p>TEXT GOES HERE</p>
</div>
</section>
</div>
)
}
The idea is to use the exposed BLOCKS and MARKS to fully customize the fetched data from Contentful like:
import { BLOCKS, MARKS } from "#contentful/rich-text-types"
import { renderRichText } from "gatsby-source-contentful/rich-text"
​
const Bold = ({ children }) => <span className="bold">{children}</span>
const Text = ({ children }) => <p className="align-center">{children}</p>
​
const options = {
renderMark: {
[MARKS.BOLD]: text => <Bold>{text}</Bold>,
},
renderNode: {
[BLOCKS.PARAGRAPH]: (node, children) => <Text>{children}</Text>,
},
},
}
​
renderRichText(node.bodyRichText, options)
Source: https://www.contentful.com/developers/docs/tutorials/general/rich-text-and-gatsby/
In that way, you can render a span with "bold" className when a MARKS.BOLD is fetched, using your own customized output.
In the snippet above, there's missing the implementation into a "standard" component, but the idea relies on the same fact. Using renderRichText what accepts two arguments:
The first one: your rich text node (aboutText in your case)
The second argument: the options with your custom output
Applied to your code, it should look like:
import React from "react"
import { useStaticQuery, graphql } from "gatsby"
import { GatsbyImage } from "gatsby-plugin-image"
import { BLOCKS, MARKS } from "#contentful/rich-text-types"
import { renderRichText } from "gatsby-source-contentful/rich-text"
import * as aboutStyles from "../styles/about.module.scss"
const options = {
renderMark: {
[MARKS.BOLD]: text => <strong>{text}</strong>,
},
renderNode: {
[BLOCKS.PARAGRAPH]: (node, children) => <p>{children}</p>,
},
}
const query = graphql`
{
contentfulAbout {
about
bioImage {
title
url
gatsbyImageData(
layout: FULL_WIDTH
placeholder: BLURRED
resizingBehavior: SCALE
width: 1000
)
}
aboutText {
raw
}
}
}
`
const AboutSection = () => {
const data = useStaticQuery(query);
const contentfulAbout = data.contentfulAbout
return (
<div className={aboutStyles.parent}>
<section className={aboutStyles.container}>
<div className={aboutStyles.image}>
<GatsbyImage className={aboutStyles.bioImage} image={contentfulAbout.bioImage.gatsbyImageData} alt={contentfulAbout.bioImage.title} />
</div>
<div className={aboutStyles.text}>
<h2>{contentfulAbout.about}</h2>
<p>{renderRichText(contentfulAbout.aboutText, options)}</p>
</div>
</section>
</div>
)
}
Of course, it may need some tweaking. Note that I've simplified the output so customize it as you wish/need.
Other resources:
https://www.contentful.com/developers/docs/concepts/rich-text/

Problem to get an image in a blogpost with NextJS and WpGraphQL

I have another question in the project I'm doing, using NextJS to create a site with a blog part in Wordpress, through WPGraphQL, and I need support in a specific part. Let's go...
I managed to pull the highlighted image with almost no problems, I broke my head a bit, but it worked. The result that's functioning is the post excerpt, the code and the query were as follows (in time: image merely to test, it was the first idea that came to my mind, the site is not about Pokemon):
Image with working image in the excerpt, codes below
NextJS code:
import { LastPosts, PostContainer } from "./Styled";
const RecentBlogPosts = ({lastPosts}) => {
const posts = lastPosts;
return (
<LastPosts>
<h1> ÚLTIMAS POSTAGENS </h1>
{posts?.map((post) => {
return (
<PostContainer key={post.slug}>
<img src={post.featuredImage?.node.sourceUrl} alt="" />
<Link href={`/post/${post.slug}`}>
<a>
<h3> { post.title } </h3>
<div dangerouslySetInnerHTML={{ __html: post.excerpt }} />
<button> Saiba mais </button>
</a>
</Link>
</PostContainer>
)
})}
</LastPosts>
)
};
export default RecentBlogPosts;
Query for this part:
export const RECENT_POSTS = `query RecentPosts {
posts(where: {orderby: {field: DATE, order: DESC}}, first: 2) {
nodes {
id
slug
title
excerpt
featuredImage {
node {
sourceUrl
}
}
}
}
}`;
But I tried to pull the same image in the full blogpsot and it wasn't working... It appears when I view the post from the generic WordPress admin template, but not at my NextJS site, which i See through localhost:3000/post/[here would be the post title in slug] that I'm using. The rest is normal, text with all fonts and specifications with styled components, as well as tags, they work without any problem. The following is the same schema: image with result, code and query that I am using, this time for the part where I'm having problems:
Image with blogpost not working, codes below
NextJS code:
import fetcher from "../../lib/fetcher";
import { GET_ALL_POSTS_WITH_SLUG, POST_BY_SLUG } from "../../lib/wordpress/api";
import { useRouter } from "next/router";
import { Reset } from "../../constants/StyledConstants";
import Header from "../../components/Header/Header";
import { BlogArticle, BlogPostContent, TagLinks, TagWrapper } from "./StyledPost";
import Footer from "../../components/Footer/Footer";
const post = ({ postData }) => {
const blogPost = postData.data.post;
console.log(postData);
const tags = postData.data.post.tags.nodes;
const router = useRouter;
if(!router.isFallback && !blogPost?.slug) {
return <div>erro</div>
}
return (
<>
<Reset />
<Header />
<BlogPostContent>
{router.isFallback ? (
<div> Carregando...... </div>
) : (
<div>
<h1> { blogPost.title } </h1>
<img src={post.featuredImage?.node.sourceUrl} alt="imagem não aparece" />
<BlogArticle dangerouslySetInnerHTML={{ __html: blogPost.content }} />
<TagWrapper>
{tags.map((tag) => <TagLinks href={`/tags/${tag.slug}`} key={tag.slug}> { tag.name } </TagLinks>)}
</TagWrapper>
</div>
)}
</BlogPostContent>
<Footer />
</>
)
}
export default post;
export async function getStaticPaths() {
const response = await fetcher(GET_ALL_POSTS_WITH_SLUG);
const allposts = await response.data.posts.nodes;
return {
paths: allposts.map((post) => `/post/${post.slug}`) || [],
fallback: false
};
}
export async function getStaticProps({ params }) {
const variables = {
id: params.slug,
idType: "SLUG"
};
const data = await fetcher(POST_BY_SLUG, { variables })
return {
props: {
postData: data
},
};
}
Query being used:
export const POST_BY_SLUG = `query PostBySlug($id: ID!, $idType: PostIdType!) {
post(id: $id, idType: $idType) {
title
slug
date
content
featuredImage {
node {
sourceUrl
}
}
tags {
nodes {
name
slug
}
}
}
}`;
I tried to use {post.featuredImage?.node.sourceUrl} because, as far as I understand, following the same basis I did for the excerpt in the blogspot, it should work, but I guess I was wrong... I tried to think of other ways to do it to get to the image, without success... Could someone help to point out where I am wrong please? Thank you very much in advance!!

using renderRichText - receiving an error Node is not defined

I am having an error "TypeError: Cannot read property 'map' of undefined" when I tried to use documentToReactComponents to output the body of contentful post. If I comment it out, and try to console log, it displays documents etc.
Having edited the code to the suggested answer, I am receiving another error "Node is not defined" using renderRichText
import React from 'react'
import Layout from '../components/layout'
import { graphql } from 'gatsby'
import Head from '../components/head'
import { BLOCKS, MARKS } from "#contentful/rich-text-types"
import { renderRichText } from "gatsby-source-contentful/rich-text"
export const query = graphql`
query($slug: String!) {
contentfulBlogPost(
slug: {
eq: $slug
}
) {
title
slug
publishedDate(formatString: "MMM Do, YYYY")
body {
raw
}
}
}
`
const Blog = (props) => {
const Bold = ({ children }) => <span className="bold">{children}</span>
const Text = ({ children }) => <p className="align-center">{children}</p>
const options = {
renderMark: {
[MARKS.BOLD]: text => <Bold>{text}</Bold>,
},
renderNode: {
[BLOCKS.PARAGRAPH]: (node, children) => <Text>{children}</Text>,
[BLOCKS.EMBEDDED_ASSET]: node => {
const alt= node.data.target.fields.title['en-US']
const url= node.data.target.fields.file['en-US'].url
return (
<>
<img src={url} alt={alt} />
</>
)
},
[BLOCKS.EMBEDDED_ENTRY]: node => {
return (
<Layout>
<Head home_title="Blog Post" />
<h1>{props.data.contentfulBlogPost.title}</h1>
<p>{props.data.contentfulBlogPost.publishedDate}</p>
{props.data.contentfulBlogPost.body}
</Layout>
)
},
},
}
renderRichText(node.bodyRichText, options)
}
export default Blog
I have tried to read through from Github but still receiving the same error. I need help.
All you need to do is to add references to the graphql query, unlike with json, the body has raw and references. With that being said, you need to append this to the body after the raw;
references{
contentful_id
title
fixed{
src
}
}
Then you can you id to search for the title and src as you can see above.
import React from 'react'
import Layout from '../components/layout'
import { graphql } from 'gatsby'
import Head from '../components/head'
import { documentToReactComponents } from '#contentful/rich-text-react-renderer'
import { BLOCKS } from "#contentful/rich-text-types"
export const query = graphql`
query($slug: String!){
post: contentfulBlogPost(slug:{eq:$slug}){
title
publishedDate(formatString: "MMMM Do, YYYY")
body{
raw
references{
contentful_id
title
fixed{
src
}
}
}
}
}
`
const Blog = (props) => {
const assets = new Map(props.data.post.body.references.map(ref => [ref.contentful_id,ref]))
const options = {
renderNode:{
[BLOCKS.EMBEDDED_ASSET]: node => {
const url = assets.get(node.data.target.sys.id).fixed.src
const alt = assets.get(node.data.target.sys.id).title
return <img alt={alt} src={url}/>
}
}
}
return(
<Layout>
<h1>{props.data.post.title}</h1>
<p>{props.data.post.publishedDate}</p>
{documentToReactComponents(JSON.parse(props.data.post.body.raw),options)}
</Layout>
)
}
export default Blog
I would suggest using the following approach:
import { BLOCKS, MARKS } from "#contentful/rich-text-types"
import { renderRichText } from "gatsby-source-contentful/rich-text"
​
const Bold = ({ children }) => <span className="bold">{children}</span>
const Text = ({ children }) => <p className="align-center">{children}</p>
​
const options = {
renderMark: {
[MARKS.BOLD]: text => <Bold>{text}</Bold>,
},
renderNode: {
[BLOCKS.PARAGRAPH]: (node, children) => <Text>{children}</Text>,
[BLOCKS.EMBEDDED_ASSET]: node => {
return (
<>
<h2>Embedded Asset</h2>
<pre>
<code>{JSON.stringify(node, null, 2)}</code>
</pre>
</>
)
},
},
}
​
renderRichText(node.bodyRichText, options)
Note: extracted from Contentful docs
Using renderRichText built-in method (from gatsby-source-contentful/rich-text). Your code seems a little bit deprecated or at least, some methods.
The following code will never work:
"embedded-asset-block" : (node) => {
const alt= node.data.target.fields.title['en-US']
const url= node.data.target.fields.file['en-US'].url
return <imag src={url} alt={alt} />
}
You need to use the providers of the dependency (#contentful/rich-text-types), that are BLOCKS and MARKS. You can always access to the dependency to check the methods, however, embedded-asset-block stands for BLOCKS.EMBEDDED_ASSET or BLOCKS.EMBEDDED_ENTRY, so:
import { BLOCKS, MARKS } from "#contentful/rich-text-types"
import { renderRichText } from "gatsby-source-contentful/rich-text"
​
const Bold = ({ children }) => <span className="bold">{children}</span>
const Text = ({ children }) => <p className="align-center">{children}</p>
​
const options = {
renderMark: {
[MARKS.BOLD]: text => <Bold>{text}</Bold>,
},
renderNode: {
[BLOCKS.PARAGRAPH]: (node, children) => <Text>{children}</Text>,
[BLOCKS.EMBEDDED_ASSET]: node => {
// manipulate here your embedded asset
return (
<>
<h2>Embedded Asset</h2>
<pre>
<code>{JSON.stringify(node, null, 2)}</code>
</pre>
</>
)
},
[BLOCKS.EMBEDDED_ENTRY]: node => {
// manipulate here your embedded entry
return (
<>
<div>I'm an embedded entry</div>
</>
)
},
},
}
​
renderRichText(node.bodyRichText, options)

Where to define variables in GraphQL

Excuse this newbie here. I want to list some articles based on their categories, now I have categories page where you click a category, a list of articles under that specific category should open. The question is, where to define the variable for the slug [which is equal to the article's category].
Where to define $slug variable which is equal to the category, given that I come from categories page, this must be a post request which sends the clicked category, I should put it somewhere on that page, can someone guide me if I make any sense here?!
Thanks in advance.
const EduCatTemp = ({data}) => {
const {allStrapiEducations:{nodes:educations}} = data
return (
<Layout>
{
educations.map((education)=> {
return (
<p>{education.title}</p>
)
})
}
</Layout>
)
}
export default EduCatTemp
export const query = graphql`
{
allStrapiEducations(filter: {education_category: {slug: {eq: $slug}}}) {
nodes {
title
}
}
}
Here is my gatsby-node.js file
educations: allStrapiEducations {
nodes {
slug
}
}
education_categories: allStrapiEducationCategories {
nodes {
slug
}
}
result.data.educations.nodes.forEach(education => {
createPage({
path: `/education/${education.slug}`,
component: path.resolve(`src/templates/education-template.js`),
context: {
slug: education.slug,
},
})
})
result.data.education_categories.nodes.forEach(educat => {
createPage({
path: `/education/${educat.slug}`,
component: path.resolve(`src/templates/education-category-template.js`),
context: {
slug: educat.slug,
},
})
})
Here is the [parent] education page which I want to take the slug from
import React from 'react'
import Layout from '../components/layout'
import EducationCard from '../components/EducationComponent/EducationCard'
import Category from '../components/utilities/Category'
const Education = ({data}) => {
const {allStrapiEducationCategories:{nodes:educations}} = data
return (
<Layout>
<div className="p-5">
<h1 className="sm:text-3xl text-2xl font-medium title-font text-gray-900 headline py-10">Beekeeping Programs</h1>
<input type="search" id="gsearch" placeholder="Search..." name="gsearch" className="my-5 py-2 search-button lg:w-1/3" />
<div className="flex flex-wrap mx-auto">
{educations.map((education)=> {
return <EducationCard headline={education.name} image={education.picture.childImageSharp.fixed} slug={`/education/${education.slug}`} />
})}
</div>
</div>
</Layout>
)
}
export default Education
export const query = graphql`
{
allStrapiEducationCategories {
nodes {
slug
picture {
childImageSharp {
fixed(width: 400
height: 200) {
...GatsbyImageSharpFixed
}
}
}
name
}
}
}
`
I somehow could reolve the issue, which was editing the Graphql query in a slightly different way, given that I had gatsby-node.js set up correctly.
export const query = graphql`
query getSingleNewsCategory($slug: String!)
{
strapiNewsCategories(slug: { eq: $slug }) {
name
}
allStrapiIndustries(
filter: {news_category: {slug: {eq: $slug}}}
) {
nodes {
report_photo {
childImageSharp {
fluid {
src
}
}
}
quote
content
title
slug
minutes_read
date(formatString: "MM")
article_author {
name
}
}
}
allStrapiNewsCategories {
nodes {
slug
name
}
}
}
`

Gatsby GraphQL error: Variable "$slug" is never used in operation "BlogPostQuery"

I am unable to pull in the data of my Ghost blog using Gatsby. I am using Ghost as my back end and I am using a package to get the Ghost blog as a source. The problem is just getting the individual posts on the page. Here is blog-post.js:
import React from "react";
export default ({ data }) => {
// const post = data.allGhostPost.edges;
return (
<div>
{/* <h1>{post.title}</h1> */}
{/* <div dangerouslySetInnerHTML={{ __html: post.html }} /> */}
</div>
);
};
export const query = graphql`
query BlogPostQuery($slug: String!) {
allGhostPost {
edges {
node {
id
slug
title
html
published_at
}
}
}
}
`;
Here is my gatsby node file:
exports.createPages = ({ graphql, boundActionCreators}) => {
const {createPage} = boundActionCreators
return new Promise((resolve, reject) => {
const blogPostTemplate = path.resolve(`src/templates/blog-post.js`)
resolve(
graphql(
`
{
allGhostPost(sort: { order: DESC, fields: [published_at] }) {
edges {
node {
id
slug
title
html
published_at
}
}
}
}
`
)
.then(result => {
result.data.allGhostPost.edges.forEach(edge => {
createPage({
path: edge.node.slug,
component: blogPostTemplate,
context: {
slug: edge.node.slug
}
})
})
return;
})
)
})
}
I figured out my problem and it was a problem with my Queries. For anyone working with the Ghost API. This is the answer you will need:
query BlogPostQuery($slug: String!) {
allGhostPost(filter: {slug: {eq: $slug}}) {
edges {
node {
id
slug
title
html
published_at
}
}
}
}
Let me explain my answer.
The issue was that my GraphQL query was not working because the $slug field was not being used within the query. It was just being passed in. That being said, I had to learn a bit of GraphQL to get to my final conclusion.
Using the GraphiQL I was able to find that the allGhostPost had a filter method. Using that I was able to pull in the right result.

Resources