skip to Main Content

I have array data in frontmatter:

tools:
  - name: Hammer
    text: Use a wooden hammer
  - name: Glue
    text: PVA glue works best for this application
  - name: Paint
    text: Choose your favourite colour

Which I can map over in my post template no trouble, say I want to get the name of each tool:

{post.frontmatter.tools.map(item => (
  <span key={item.name}>{item.name}</span>
))}

I am passing this array to my SEO component like this:

<SEO tools={post.frontmatter.tools} />

And then in my SEO component I have access to all of it there, here’s the guts of what I have in my component but the map function doesn’t work here and I’m not sure why?

import React from "react"
import Helmet from "react-helmet"

const SEO = ({
  type,
  tools,
}) => {

  const seo = {
    type: type,
    howToTools: tools,
  }

  let schemaTools = seo.howToTools
  var schemaToolsWithType = schemaTools.map(function(item) {
    item.@type = "HowToTool"
    return item
  })

  console.log(newArray)

  const schemaType = seo.type

  let schemaHowTo = null

  if (schemaType === "howto") {
    schemaHowTo = {
      /* HowTo Schema */
      "@context": "http://schema.org",
      "@type": "HowTo",

      tool: schemaToolsWithType,

    }
  }

  let schemaArticle = null

  if (schemaType === "article") {
    schemaArticle = {
      {/* Article schema here */}
    }
  }

  return (
    <>
      <Helmet>
        {/* Insert schema.org data conditionally (HowTo/Article)*/}
        {schemaType === "howto" && (
          <script type="application/ld+json">
            {JSON.stringify(schemaHowTo)}
          </script>
        )}
        {schemaType === "article" && (
          <script type="application/ld+json">
            {JSON.stringify(schemaArticle)}
          </script>
        )}
      </Helmet>
    </>
  )
}

export default SEO

What I am wanting to achieve is get my array data from front matter, and to each object in the array add the schema type property so the final outcome would be:

[
    {
      "@type": "HowToTool",
      "name": "Hammer",
      "text: "Use a wooden hammer"
    },
    {
      "@type": "HowToTool",
      "name": "Glue",
      "text: "PVA glue works best for this application"
    },
    {
      "@type": "HowToTool",
      "name": "Hammer",
      "text: "Choose your favourite colour"
    }
  ]

Update:

I’m able to print valid JSON+ld to my head with the following approach but I still have the pesky cannot read property ‘map’ of null in my browser.

  let schemaHowToTools = []

  seo.howToTools.map(tool =>
    schemaHowToTools.push({
      "@type": "HowToTool",
      name: tool.name,
      text: tool.text,
    })
  )

Update #2 (Working solution)

Thanks to suggestions from @gazdagergo this was the solution that worked for my situation. The optional chaining looks better yet but at this point requires an extra plugin.

  const schemaTools =
    seo.howToTools &&
    seo.howToTools.map &&
    seo.howToTools.map(tool => {
      const schemaTypeTool = {
        "@type": "HowToTool",
      }
      return Object.assign(schemaTypeTool, tool)
    })

3

Answers


  1. Maybe we should run a check to see if the array is actually there. Something like this.

    if(seo.howToTools.length > 0) {       
          let schemaTools = seo.howToTools
          var schemaToolsWithType = schemaTools.map(function(item) {
            item.@type = "HowToTool"
            return item
          })
    }else{
        return ( <div>Loading</div>)
    
    Login or Signup to reply.
  2. You need to understand that javascript is a dynamic language. At the initial point of declaration of tools in your component. It is only a variable with the value of undefined. I am guessing that your tools array is coming from the database somewhere which is an asynchronous activity.
    I would usually approach this problem like this:

    1. Harness the power of ES6 and assign a default value to tools at the point of its destructuring, so you could have something like tools=[]
      This value would always change later after the real value of tools is passed to it by whatever component is calling SEO.

    2. At the point where you map through the value of tools. You do a length check first to see if tools is empty or has values.
      If it’s empty….have something to return (something like tools currently has no values)
      If it isn’t, then you can return the product of the mapping.

    This would have been easier if you were using a state management tool like redux where you can ascertain if tools is only empty at the moment because you have gotten a reaponse back from the db. In this case you can return Loading. This is also possible with react, create a state [loading, setLoading] that depends on the response from the server. Let ne know if there is anymore explanation needed.

    Login or Signup to reply.
  3. I’d suggest to use a safe navigation to avoid errors like this. E.g.:

    const result = tools && tools.map && tools.map(item => { return // etc })
    

    this will map on tools only when tools is really an array.

    Or even better, you can use now optional chaining in JS:

    const result = tools?.map(item => { return // etc })
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search