all posts

Gatsby: Paging Tags and Posts in Categories, Part 2

Published to Blog on 8 Mar 2018

Gatsby

This is the second post explaining how I implemented paged posts and tags by category on this Gatsby site using gatsby-pagination. In Part 1 I showed most of the structure of gatsby-node.js and how to create pages using Gatsby’s createPage action creator and gatsby-pagination’s createPaginationPages function.

Now let’s take a look at the category and tag pages templates.

Category Template

In the previous post I showed the filter from the graphql query below. Following is the remainder of the relevent working code from the category template. There is not much here really other than the query itself. The bulk of the work is done by the CategoryTemplateDetails component explained the next section.

class CategoryTemplate extends React.Component {
  render() {
    const { category } = this.props.pathContext;

    return (
      <div>
        <Helmet title={`${category}`} />
        <CategoryTemplateDetails {...this.props} />
      </div>
    );
  }
}

export default CategoryTemplate;

export const pageQuery = graphql`
  query CategoryPage($nodes: [String]) {
    allMarkdownRemark(
        filter: { fields: { ids: { in: $nodes } } },
        sort: { order: DESC, fields: [frontmatter___date] }
      ){
      edges {
        node {
          id
          excerpt(pruneLength: 250)
          fields {
            categoryPath
          }
          frontmatter {
            path
            title
            date
            category
            cover
          }
        }
      }
    }
  }
`;

Below is category template component. All the information needed for paging comes from props.pathContent. These are in turned passed to the Pagination component. The posts come through from the template as props.data.allMarkdownRemark.edges. Once we have all that data the only thing left to do is render each of the post extracts which is done with the Post component. The Post component is used here, in the TagDetailsTemplate and in the search page.

class CategoryTemplateDetails extends React.Component {
  render() {
    const { category, page, prev, next, pages, total } = this.props.pathContext;
    const posts = this.props.data.allMarkdownRemark.edges;
    const items = [];
    posts.forEach((post) => {
      items.push(<Post data={post} key={post.node.id} />);
    });

    return (
      <div className="page">
        <div className="page_subheader">
          { total } post{ total === 1 ? '' : 's' } in
        </div>
        <h1 className="page__title">{ category }</h1>
        <div className="page__body">
          {items}
        </div>
        <div className="page__pagination">
          <Pagination
            page={page}
            pages={pages}
            prev={prev}
            next={next}
            total={total}
            prevText="<< Newer Posts"
            nextText="Older Posts >>"
          />
        </div>
      </div>
    );
  }
}

Tag Template

In the tag template we are rendering one of two different results based on the input. If tag has a value then this page should be rendering all the posts for that tag. We will do that with the TagTemplateDetails component. If tag does not have a value then we are rendering the tags in the category. We will do that with the TagsTemplateDetails component.

class TagTemplate extends React.Component {
  render() {
    const { tag, category } = this.props.pathContext;

    if (tag) {
      return (
        <div>
          <Helmet title={`All Posts tagged as "${tag}"`} />
          <TagTemplateDetails {...this.props} />
        </div>
      );
    } else {
      return (
        <div>
          <Helmet title={`Tags in ${category}`} />
          <TagsTemplateDetails {...this.props} />
        </div>
      );
    }
  }
}

export default TagTemplate;

export const pageQuery = graphql`
  query TagPage($nodes: [String]) {
    allMarkdownRemark(
        filter: { fields: { ids: { in: $nodes } } },
        sort: { order: DESC, fields: [frontmatter___date] }
      ){
      edges {
        node {
          id
          excerpt(pruneLength: 250)
          fields {
            categoryPath
          }
          frontmatter {
            title
            path
            date
            category
            cover
          }
        }
      }
    }
  }
`;

The TagTemplateDetails components is very similar to the CategoryTemplateDetails component above.

class TagTemplateDetails extends React.Component {
  render() {
    const { tag, page, prev, next, pages, total } = this.props.pathContext;
    const items = [];
    const posts = this.props.data.allMarkdownRemark.edges;
    posts.forEach((post) => {
      items.push(<Post data={post} key={post.node.id} />);
    });

    return (
      <div className="page">
        <h1 className="page__title">
          { total } post{ total === 1 ? '' : 's' } tagged as &quot;{tag}&quot;
        </h1>
        <div className="page__body">
          {items}
        </div>
        <div className="page__pagination">
          <Pagination
            page={page}
            pages={pages}
            prev={prev}
            next={next}
            total={total}
            prevText="<< Newer Posts"
            nextText="Older Posts >>"
          />
        </div>
      </div>
    );
  }
}

The TagsTemplateDetails is a bit different from the previous two examples because instead of displaying posts it is instead displaying tags. However, the concepts are the same. Everything we need for paging the tags in the category is included in the props.pathContext with nodes being the array of tags to render on the page.

class TagsTemplateDetails extends React.Component {
  render() {
    const { category, nodes, page, pages, prev, next, total } = this.props.pathContext;

    return (
      <div className="page">
        <h1 className="page__title">Tags in { category }</h1>
        <div className="page__body">
          { nodes.map((tagName) => {
            return (
              <div className="post">
                <h2 className="post__title">
                  <Link className="post__title-link" to={`/${_.kebabCase(category)}/tags/${tagName}`}>{tagName}</Link>
                </h2>
              </div>
            );
          })}
        </div>
        <div className="page__pagination">
          <Pagination
            page={page}
            pages={pages}
            prev={prev}
            next={next}
            total={total}
          />
        </div>
      </div>
    );
  }
}

That is it for the templates themselves. In the next post we will examine the Pagination component.


Dan Hounshell
Web geek, nerd, amateur maker. Likes: apis, node, motorcycles, sports, chickens, watches, food, Nashville, Savannah, Cincinnati and family.
Dan Hounshell on Twitter