Pass props into React.Component

Hello!

I am having trouble figuring out how to get a prop sent to a component. Simply I want the component to list posts that are filtered by my criteria.

For example

<AuthorRoll name={title} />

Would take in the Author’s name and filter posts that are for that Author only.

Here is my AuthorRoll.js page

import React from "react"
import PropTypes from "prop-types"

// Components
import { Link, graphql, StaticQuery } from "gatsby"
import PreviewCompatibleImage from '../preview-compatible-image'
import './rolls.scss'

class AuthorRoll extends React.Component {
  render() {
    const { data } = this.props
    const { edges: posts, totalCount: tcount } = data.allMarkdownRemark
    const { author } = "test"
    const tagHeader = `${tcount} post${
      tcount === 1 ? "" : "s"
    } tagged with "${author}"`

    return (
      <div>
      <h1 className="roll-header">{tagHeader}</h1>
      {posts &&
        posts.map(({ node: post }) => (
            <article
              className={`roll-post-container ${
                post.frontmatter.featuredpost ? 'is-featured' : ''
              }`}
            >
              <div className="roll-post-content">
                <span className="roll-category">Category</span>
                <Link
                  className="roll-title"
                  to={post.fields.slug}
                >
                  {post.frontmatter.title}
                </Link>
                <Link
                  className="roll-details roll-excerpt"
                  to={post.fields.slug}
                >
                  {post.excerpt}
                </Link>
                <div>
                  <div className="roll-author">
                    {post.frontmatter.author}
                  </div>
                  <div className="roll-details">
                    <span>{post.frontmatter.date} ·&nbsp;</span>
                    <span>{post.timeToRead} min read</span>
                  </div>
                </div>
              </div>
              <div className="roll-image-container">
                {post.frontmatter.featuredimage ? (

                    <Link
                      className="roll-post-image"
                      to={post.fields.slug}
                    >
                    <PreviewCompatibleImage
                      imageInfo={{
                        image: post.frontmatter.featuredimage,
                        alt: `featured image thumbnail for post ${post.frontmatter.title}`,
                      }}
                    />
                    </Link>

                ) : null}
              </div>
            </article>
        ))}
      </div>
    )
  }
}

AuthorRoll.propTypes = {
  data: PropTypes.shape({
    allMarkdownRemark: PropTypes.shape({
      totalCount: PropTypes.number.isRequired,
      edges: PropTypes.arrayOf(
        PropTypes.shape({
          node: PropTypes.shape({
            frontmatter: PropTypes.shape({
              title: PropTypes.string.isRequired,
              author: PropTypes.string.isRequired,
            }),
            fields: PropTypes.shape({
              slug: PropTypes.string.isRequired,
            }),
          }),
        }).isRequired
      ),
    }),
  }),
}

export default () => (
  <StaticQuery
    query={graphql`
      query($author: String) {
        allMarkdownRemark(
          limit: 2000
          sort: { fields: [frontmatter___date], order: DESC }
          filter: {frontmatter: {templateKey: {eq: "blog-post"}, author: {in: "[$author]"}}}
        ) {
        totalCount
        edges {
          node {
            excerpt(pruneLength: 400)
            fields {
              slug
            }
            timeToRead
            frontmatter {
              title
              templateKey
              author
              date(formatString: "MMMM DD, YYYY")
              featuredimage {
                childImageSharp {
                  fluid(maxWidth: 120, quality: 100) {
                    ...GatsbyImageSharpFluid
                  }
                }
              }
            }
          }
        }
      }
    }
    `}
    render={(data, count) => <AuthorRoll data={data} count={count} />}
  />
)

Is it possible to reference a prop from the parent and use it in this child component? If I manually add the filter to the author name I get the posts I want but it would be nice to reference the author name from the parent page.

Hi @byebyers, this is more of a React question than a CMS question but I’ll give it a try anyway. I might be missing something here, but if you set a <AuthorRoll name={title} /> in the parent you should be able to access it in the child via this.props.name.
Do you want to use the author name is the query? I think you’ll need to use a page query instead of a static query: https://www.gatsbyjs.org/docs/static-query/#how-staticquery-differs-from-page-query

You are right about using a page query instead of a static one. I found the issue was really how I was passing props into the component. Turns out I needed to make an additional query on the page I wanted to show the filtered posts on.

For example, for an Author Page I needed to find a way to pass the author name into my query to only show blog posts for that author. Since my site already creates pages for my Authors I needed to add the Title of the page to my Gatsby-Node Create pages context.

posts.forEach(edge => {
      const id = edge.node.id
      const title = edge.node.frontmatter.title
      createPage({
        path: edge.node.fields.slug,
        tags: edge.node.frontmatter.tags,
        component: path.resolve(
          `src/templates/${String(edge.node.frontmatter.templateKey)}.js`
        ),
        // additional data can be passed via context
        context: {
          id,
          title,
        },
      })

Even though this code snippet says posts it still used to create pages. With Title in the context allows me to use as a variable in my query.

export const query = graphql`
  query AuthorPage($id: String!, $title: String) {
    authorPage: markdownRemark(id: { eq: $id } frontmatter: { templateKey: { eq: "author-page" } }) {
      html
      frontmatter {
        title
        description
        thumbnail {
          childImageSharp {
            fluid(maxWidth: 2048, quality: 100) {
              ...GatsbyImageSharpFluid
            }
          }
        }
      }
    }
    authorPosts: allMarkdownRemark(
      limit: 2000
      sort: { fields: [frontmatter___date], order: DESC }
      filter: {frontmatter: {templateKey: {eq: "blog-post"}, author: { in: [$title] }}}
    ) {
      totalCount
      edges {
        node {
          excerpt(pruneLength: 400)
          fields {
            slug
          }
          timeToRead
          frontmatter {
            title
            templateKey
            author
            date(formatString: "MMMM DD, YYYY")
            featuredimage {
              childImageSharp {
                fluid(maxWidth: 120, quality: 100) {
                  ...GatsbyImageSharpFluid
                }
              }
            }
          }
        }
      }
    }
  }

In this query I check the $title as a string and filter it in my “authorPosts” query. That way I can get the blog posts i want for that author. The next part is pulling that data into the “blog-roll” component.

<BlogRoll data={data.authorPosts} />

This is the same as “data.allMarkDownRemark” but since it is a second query I had to label it “authorPosts” instead.

Now the correct data is sent to the component and I don’t have to make an additional query there which saves time. My blog-roll component looks like this for reference.

const BlogRoll = ({ data }) => {
  const { edges: posts } = data
  return (
    <div>
      {posts &&
        posts.map(({ node: post }) => (
            <article
              className={`roll-post-container ${
                post.frontmatter.featuredpost ? 'is-featured' : ''
              }`}
            >
              <div className="roll-post-content">
                <span className="roll-category">Category</span>
                <Link
                  className="roll-title"
                  to={post.fields.slug}
                >
                  {post.frontmatter.title}
                </Link>
                <Link
                  className="roll-details roll-excerpt"
                  to={post.fields.slug}
                >
                  {post.excerpt}
                </Link>
                <div>
                  <div className="roll-author">
                    <Link to={`/authors/authors-${kebabCase(post.frontmatter.author)}/`} className="text-black">
                      {post.frontmatter.author}
                    </Link>
                  </div>
                  <div className="roll-details">
                    <span>{post.frontmatter.date} ·&nbsp;</span>
                    <span>{post.timeToRead} min read</span>
                  </div>
                </div>
              </div>
              <div className="roll-image-container">
                {post.frontmatter.featuredimage ? (

                    <Link
                      className="roll-post-image"
                      to={post.fields.slug}
                    >
                    <PreviewCompatibleImage
                      imageInfo={{
                        image: post.frontmatter.featuredimage,
                        alt: `featured image thumbnail for post ${post.frontmatter.title}`,
                      }}
                    />
                    </Link>

                ) : null}
              </div>
            </article>
        ))}
    </div>
  )
}

BlogRoll.propTypes = {
  data: PropTypes.shape({
    allMarkdownRemark: PropTypes.shape({
      edges: PropTypes.array,
    }),
  }),
}

export default BlogRoll

And that’s it! My author page shows blog-posts only related to that author. It also simplified the blog-roll component so I can use filters like tags and categories. Pretty neat!

1 Like

Great to hear that and thank you for sharing your solution!

1 Like