sbardian/gatsby-plugin-breadcrumb

Am getting double names in the url

davidkartuzinski opened this issue · 4 comments

Thank you very much for your plugin. Coming from WordPress and SEO in general, I find breadcrumbs to be vital in my battle with our friend Mr. Google. So, thank you for this plugin. (However, I am relatively new to JavaScript and React.)

Having said that, I am getting a weird issue.

For my page that lists all blog posts with a specific tag (or a specific Category), I am expecting:

home/tags/green-tea

I am getting

home/tags/tags/green-tea

Same for categories. I am getting home/categories/categories/health

This is the repo: https://github.com/davidkartuzinski/1001teafacts
Please forgive the look, if you clone, as it's a bit naked (no CSS) until I get all the functionality in place.

I have looked through the issues here that seemed possibly related and found nothing.

I also tried to make it it's own component but failed miserably. (I saw some stuff in this repo that will help, once this below issue is taken care of.)

--

Below is the relevant code:

src/templates/tags.js

import React from "react"
import PropTypes from "prop-types"
import SiteMetaData from "../components/site-metadata"
import { IoIosPricetags } from "react-icons/io"

import { Link, graphql } from "gatsby"
import { Breadcrumb } from "gatsby-plugin-breadcrumb"

const Tags = ({ pageContext, data, location }) => {
  const { tag } = pageContext
  const { edges, totalCount } = data.allMdx
  const tagHeader = `${totalCount} post${
    totalCount === 1 ? "" : "s"
  } tagged with "${tag}"`

  const {
    breadcrumb: { crumbs },
  } = pageContext

  const customCrumbLabel = location.pathname.toLowerCase().replace("-", " ")

  return (
    <div>
      <SiteMetaData />
      <h1>
        <IoIosPricetags />
        {tagHeader}
      </h1>

      <div>
        {" "}
        You are here:
        <Breadcrumb
          crumbs={crumbs}
          crumbSeparator=""
          crumbLabel={customCrumbLabel}
        />
      </div>
      <ul>
        {edges.map(({ node }) => {
          const { slug } = node.fields
          const { title } = node.frontmatter
          return (
            <li key={slug}>
              <Link to={slug}>{title}</Link>
            </li>
          )
        })}
      </ul>
      <Link to="/tags">See all tags</Link>
    </div>
  )
}

Tags.propTypes = {
  pageContext: PropTypes.shape({
    tag: PropTypes.string.isRequired,
  }),
  data: PropTypes.shape({
    allMdx: PropTypes.shape({
      totalCount: PropTypes.number.isRequired,
      edges: PropTypes.arrayOf(
        PropTypes.shape({
          node: PropTypes.shape({
            frontmatter: PropTypes.shape({
              title: PropTypes.string.isRequired,
            }),
            fields: PropTypes.shape({
              slug: PropTypes.string.isRequired,
            }),
          }),
        }).isRequired
      ),
    }),
  }),
}

export default Tags
export const pageQuery = graphql`
  query($tag: String) {
    allMdx(
      limit: 2000
      sort: { fields: [frontmatter___date], order: DESC }
      filter: { frontmatter: { tags: { in: [$tag] } } }
    ) {
      totalCount
      edges {
        node {
          frontmatter {
            title
          }
          fields {
            slug
          }
        }
      }
    }
  }
`

gatsby-node.js

const path = require(`path`)
const _ = require(`lodash`)

const { createFilePath } = require(`gatsby-source-filesystem`)
const { paginate } = require(`gatsby-awesome-pagination`)

exports.onCreateNode = ({ node, getNode, actions }) => {
  const { createNodeField } = actions
  if (node.internal.type === `Mdx`) {
    const slug = createFilePath({ node, getNode, basePath: `pages` })
    createNodeField({
      node,
      name: `slug`,
      value: slug,
    })
  }
}

exports.createPages = async ({ graphql, actions }) => {
  const { createPage } = actions

  // create variable to use for each type of templates
  const blogPostTemplate = path.resolve(`./src/templates/blog-post.js`)
  const tagTemplate = path.resolve(`./src/templates/tags.js`)
  const categoriesTemplate = path.resolve(`./src/templates/categories.js`)
  // const blogListTemplate = path.resolve(`./src/templates/blog.js`)

  const result = await graphql(`
    query {
      postsRemark: allMdx(
        sort: { order: DESC, fields: [frontmatter___date] }
        limit: 2000
      ) {
        edges {
          node {
            fields {
              slug
            }
            frontmatter {
              tags
              categories
            }
          }
          next {
            frontmatter {
              slug
              title
            }
          }
          previous {
            frontmatter {
              slug
              title
            }
          }
        }
      }
      tagsGroup: allMdx(limit: 2000) {
        group(field: frontmatter___tags) {
          field
          fieldValue
        }
      }
      categoriesGroup: allMdx(limit: 2000) {
        group(field: frontmatter___categories) {
          field
          fieldValue
        }
      }
    }
  `)
  // handle errors
  if (result.errors) {
    reporter.panicOnBuild(
      `Error while running GraphQL query. Check gatsby-node.js file.`
    )
    return
  }

  const posts = result.data.postsRemark.edges
  posts.forEach(({ node, next, previous }) => {
    createPage({
      path: node.fields.slug,
      component: blogPostTemplate,
      context: {
        // Data passed to context is available
        // in page queries as GraphQL variables.
        slug: node.fields.slug,

        // https://toripugh.com/blog/gatsby-blog--next-and-previous-links
        next,
        previous,
      },
    })
  })

  // http://codekarate.com/daily-dose-of-drupal/gatsby-pagination-gatsby-awesome-pagination

  paginate({
    createPage,
    items: posts,
    itemsPerPage: 2, // number of pages
    pathPrefix: "/blog",
    component: path.resolve(`./src/templates/blog.js`),
  })

  // extract tag data from the query
  const tags = result.data.tagsGroup.group
  // make the tag pages
  tags.forEach(tag => {
    createPage({
      path: `/tags/${_.kebabCase(tag.fieldValue)}/`,
      component: tagTemplate,
      context: {
        tag: tag.fieldValue,
      },
    })
  })

  // extract category data from the query
  const categories = result.data.categoriesGroup.group
  // make the category pages
  categories.forEach(category => {
    createPage({
      path: `/categories/${_.kebabCase(category.fieldValue)}/`,
      component: categoriesTemplate,
      context: {
        category: category.fieldValue,
      },
    })
  })
}

Again, thank you very much for your attention and help.

-DK

@davidkartuzinski thanks for the issue! I'll take a look and get back to you!

@davidkartuzinski well, the issue is you are trusting my example of a customCrumbLabel from the docs to work with your app :)

this line:
https://github.com/davidkartuzinski/1001teafacts/blob/8a647f9f3f890d0832edadf43e05daccbfa10314/src/templates/tags.js#L20

and this line:
https://github.com/davidkartuzinski/1001teafacts/blob/8a647f9f3f890d0832edadf43e05daccbfa10314/src/templates/categories.js#L20

In your situation on this template, that is getting you the full path instead of the last section (crumb) of the path.

Try something like this instead, note: this will only get the last crumb section of the path in this template situation. (ie path of /tags/ginger-tea)

const [, , customCrumbLabel] = location.pathname.split("/")
// you can now manipulate customCrumbLabel here ex: customCrumbLabel.replace('-', ' ') 

If this fixes you up (it should) feel free to close the issue or let me know and I can close it up.

I'll review those docs and see if I can tweak them to make them better. Any suggestions welcome on that if you have any ideas to clear things up.
thanks!

@sbardian Thank you. This code definitely solved the issue. I would love to try and make a pull request on the docs, however, I am not sure if I fully understand why the solution works, and therefore not sure if I can do a good job. Could you confirm my understanding below? and then I can propose something to you for the docs.

Looking at this snippet:

const [,  , customCrumbLabel] = location.pathname.split("/")

From what I understand, the plugin is using the location object to create the customCrumbLabel. The location from the gatsby.node.js file is reporting this object:

{
href: "http://localhost:8000/tags/digestion/", ancestorOrigins: DOMStringList, origin: "http://localhost:8000", protocol: "http:", host: "localhost:8000", …}
href: "http://localhost:8000/tags/digestion/"
ancestorOrigins: DOMStringList {length: 0}
origin: "http://localhost:8000"
protocol: "http:"
host: "localhost:8000"
hostname: "localhost"
port: "8000"
pathname: "/tags/digestion/"
search: ""
hash: ""
assign: ƒ assign()
reload: ƒ reload()
toString: ƒ toString()
replace: ƒ replace()
state: null
key: "initial"
__proto__: Object
}

The "problem" is that the breadCrumb is then created from pageContext, which returns as:

{
tag: "digestion", breadcrumb: {…}}
tag: "digestion"
breadcrumb: {location: "/tags/digestion/", crumbs: Array(3)}
__proto__: Object
}

... and we want only "digestion".

So by splitting the location/pathname /tags/digestion at the "/" the value of customCrumbLabel becomes digestion". Which we then add the existing "crumbs" and get the correct value. (The array becomes ["", "tags", "digestion", ""]```.

Which is why later in the component it works for crumbLabel={customCrumbLabel}.

--

After typing all this out, it seems correct to me. And really just about understanding JavaScript. I would dare say that maybe the docs don't need to be changed and if anyone has this issue, they will probably just find this issue?

I am closing the issue as the issue is closed and will see what you want me to do. Thank you very much.

@davidkartuzinski no problem. I'll look into removing or just making that line in the example a note, that might help. thanks!