Graphql and custom previews

Hello there,

So I have been scratching my head about how to pull queries into custom previews or if its even possible. This has been going on with several of my templates but Ill use a blog post as an example.

Here is a pic of the blog page rendering correctly

This is the same content rendered on custom previews

So there is a few issues here relating to my query. First is the date of the post. In my query I have the date formatted in the front matter as such.

date(formatString: "MMMM DD, YYYY")

And I pull the date like this

featuredimage={post.frontmatter.featuredimage}

This renders correctly on my blog page
image

But the preview template threw this error.

`Objects are not valid as a React child`

In order to get around this I had to change the data in my preview file to this.

date={data.date.toString()}

But this leaves this in the preview pane

So the formatting from my blog post is not rendering like I thought it would in custom previews. Neither is the author thumbnail and timeToRead from the query. So I believe the issue revolves around whether or not custom previews can take queries.

Here are the related files

CMS.js

import CMS from 'netlify-cms-app'

import typography from '../utils/typography'

import IndexPagePreview from './preview-templates/IndexPagePreview'
import AboutPagePreview from './preview-templates/AboutPagePreview'
import BlogPagePreview from './preview-templates/BlogPagePreview'
import AuthorPagePreview from './preview-templates/AuthorPagePreview'
import BlogPostPreview from './preview-templates/BlogPostPreview'

CMS.registerPreviewTemplate('home', IndexPagePreview)
CMS.registerPreviewTemplate('about', AboutPagePreview)
CMS.registerPreviewTemplate('blog', BlogPagePreview)
CMS.registerPreviewTemplate('authors', AuthorPagePreview)
CMS.registerPreviewTemplate('blogPost', BlogPostPreview)


CMS.registerPreviewStyle(typography.toString(), { raw: true })

BlogPostPreview.js

iimport React from 'react'
import PropTypes from 'prop-types'
import { BlogPostTemplate } from '../../templates/blog-post'

const BlogPostPreview = ({ entry, widgetFor }) => {
  const data = entry.getIn(['data']).toJS()
  const tags = entry.getIn(['data', 'tags'])
  console.log(data)
  if (data) {
    return (
      <BlogPostTemplate
        content={data.body}
        description={data.description}
        featuredimage={data.featuredimage}
        author={data.author}
        thumbnail={data.thumbnail}
        timeToRead={data.timeToRead}
        date={data.date.toString()}
        tags={tags && tags.toJS()}
        title={data.title}
      />
    )
  } else {
    return <div>Loading...</div>
  }
}

BlogPostPreview.propTypes = {
  entry: PropTypes.shape({
    getIn: PropTypes.func,
  }),
  getAsset: PropTypes.func,
}

export default BlogPostPreview

Blog-Post.js

import React from 'react'
import PropTypes from 'prop-types'
import Helmet from 'react-helmet'
import { graphql, Link } from 'gatsby'
import Layout from '../components/layout/layout'
import Container from '../components/container/container'
import PreviewCompatibleImage from '../components/preview-compatible-image'
import Content, { HTMLContent } from '../components/content/content'
import { kebabCase } from 'lodash'
import TagSection from '../components/tag-section/tag-section'
import './blog-post.scss'

export const BlogPostTemplate = ({
  content,
  contentComponent,
  description,
  featuredimage,
  author,
  thumbnail,
  date,
  timeToRead,
  tags,
  title,
  helmet,
}) => {
  const PostContent = contentComponent || Content

  return (
    <Container>
      <section>
        {helmet || ''}
        <h1 className="post-title">{title}</h1>
        <p className="post-subheader">{description}</p>
        <div className="post-details">
          <div className="post-author-pic">
            <Link to={`/authors/authors-${kebabCase(author)}/`}>
              <PreviewCompatibleImage
                imageInfo={{
                  image: thumbnail,
                  alt: `featured image thumbnail for post ${title}`,
                }}
              />
            </Link>
          </div>
          <div className="post-detail-block">
            <div className="post-author">
              <Link to={`/authors/authors-${kebabCase(author)}/`} className="text-black">
                {author}
              </Link>
            </div>
            <div className="post-fine-details">
              <span>{date} ·&nbsp;</span>
              <span>{timeToRead} min read</span>
            </div>
          </div>
        </div>
        <div className="post-image">
          <PreviewCompatibleImage
            imageInfo={{
              image: featuredimage,
              alt: `featured image thumbnail for post ${title}`,
            }}
          />
          <div className="post-fine-details photo-details">
            <span>Photo Credit placed here</span>
          </div>
        </div>
        <div className="post-text">
          <PostContent content={content} />
        </div>
        <TagSection tags={tags} />
      </section>
    </Container>
  )
}

BlogPostTemplate.propTypes = {
  content: PropTypes.node.isRequired,
  contentComponent: PropTypes.func,
  description: PropTypes.string,
  featuredimage: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
  author: PropTypes.string,
  thumbnail: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
  title: PropTypes.string,
  helmet: PropTypes.object,
}

const BlogPost = ({ data }) => {
  const { markdownRemark: post } = data
  const postDate = post.frontmatter.date
  const stringDate = postDate.toString()
  console.log(post.frontmatter.featuredimage)
  return (
    <Layout>
      <BlogPostTemplate
        content={post.html}
        contentComponent={HTMLContent}
        description={post.frontmatter.description}
        featuredimage={post.frontmatter.featuredimage}
        author={post.frontmatter.author}
        thumbnail={post.fields.author.frontmatter.thumbnail}
        date={stringDate}
        timeToRead={post.timeToRead}
        helmet={
          <Helmet titleTemplate="%s | Blog">
            <title>{`${post.frontmatter.title}`}</title>
            <meta
              name="description"
              content={`${post.frontmatter.description}`}
            />
          </Helmet>
        }
        tags={post.frontmatter.tags}
        title={post.frontmatter.title}
      />
    </Layout>
  )
}

BlogPost.propTypes = {
  data: PropTypes.shape({
    markdownRemark: PropTypes.object,
  }),
}

export default BlogPost

export const pageQuery = graphql`
  query BlogPostByID($id: String!) {
    markdownRemark(id: { eq: $id }) {
      id
      html
      timeToRead
      fields {
        author {
          frontmatter {
            thumbnail {
              childImageSharp {
                fluid(maxWidth: 120, quality: 100) {
                  ...GatsbyImageSharpFluid
                }
              }
            }
          }
        }
      }
      frontmatter {
        date(formatString: "MMMM DD, YYYY")
        title
        description
        featuredimage {
          childImageSharp {
            fluid(maxWidth: 120, quality: 100) {
              ...GatsbyImageSharpFluid
            }
          }
        }
        author
        tags
      }
    }
  }
`

What I do understand is that the data is brought in as an object into the preview file like this.

Object { body: "Body Testdsafdasf adsf", templateKey: "blog-post", author: "Jacob Byers", featuredimage: "/img/fiver4.jpg", featuredpost: false, date: Date Tue Feb 25 2020 12:50:14 GMT-0700 (Mountain Standard Time), title: "My First Blog", tags: (2) […], description: "Description Test test" }

But I dont understand why thumbnail and timeToRead arent in there. Or why the date is formated differently than in my query. If I add the query to the preview file I dont get any results either. Is there another way to access my query results?

Hi @byebyers, the CMS returns a date object for dates, so you’ll need to format it yourself. Using moment (for example) you could use:

import moment from 'moment'

moment(data.date).format('MMMM DD, YYYY')

As for queries, the results of Gatsby queries are generated during build time from the markdown files, so they won’t be available in your custom previews.

You can access the relation information via fieldsMetaData

import React from 'react'
import PropTypes from 'prop-types'
import { BlogPostTemplate } from '../../templates/blog-post'

const BlogPostPreview = ({ entry, widgetFor, fieldsMetaData }) => {
  const data = entry.getIn(['data']).toJS()
  const tags = entry.getIn(['data', 'tags'])
  console.log(data)
  console.log(fieldsMetaData.toJS())
}

Thanks for showing moment with an example.

As for your point on queries

That makes sense. For example, timeToRead would have to render in real-time when writing the article. Not realistic for a static site generator.

fieldsMetaData did get me the thumbnail for the author of the post. This was because fieldsMetaData displayed my Author Object in the fields section of MarkdownRemark. For those who dont understand how to add objects to fields feel free to reference this question.

To get my author thumbnail I added fieldsMetaData to my BlogPostPreview file like this

import React from 'react'
import PropTypes from 'prop-types'
import { BlogPostTemplate } from '../../templates/blog-post'
import moment from 'moment'

const BlogPostPreview = ({ entry, widgetFor, fieldsMetaData  }) => {
  const data = entry.getIn(['data']).toJS()
  const tags = entry.getIn(['data', 'tags'])
  const postDate = moment(data.date).format('MMMM DD, YYYY')
  const fields = fieldsMetaData.toJS()
  console.log(fields)

  if (data && tags  && fields.author) {
    const authorObj = fields.author.authors
    const postAuthor = data.author
    const authorData = authorObj[postAuthor]
    const authorThumb = authorData.thumbnail
    return (
      <BlogPostTemplate
        content={data.body}
        description={data.description}
        featuredimage={data.featuredimage}
        author={postAuthor}
        thumbnail={authorThumb}
        date={postDate}
        tags={tags && tags.toJS()}
        title={data.title}
      />
    )
  } else {
    return <div>Loading...</div>
  }
}

BlogPostPreview.propTypes = {
  entry: PropTypes.shape({
    getIn: PropTypes.func,
  }),
  getAsset: PropTypes.func,
}

export default BlogPostPreview

I started with converting fieldsMetaData to JS

const fields = fieldsMetaData.toJS()

But printing fields gave me 3 objects in the console.
image

Not sure why that happened but I was able to access the last object in my if statement.

if (data && tags  && fields.author) {

However fields.author gives me an object with an author object inside.

And that object’s key was a string. So I was able to access that info by doing the following.

    const authorObj = fields.author.authors
    const postAuthor = data.author
    const authorData = authorObj[postAuthor]
    const authorThumb = authorData.thumbnail

With this I was able to get my author’s thumbnail with the authorThumb variable.

1 Like

thanks very much for sharing, @byebyers!