using renderRichText - receiving an error Node is not defined

Viewed 1246

I am having an error "TypeError: Cannot read property 'map' of undefined" when I tried to use documentToReactComponents to output the body of contentful post. If I comment it out, and try to console log, it displays documents etc. Having edited the code to the suggested answer, I am receiving another error "Node is not defined" using renderRichText

import React from 'react'
import Layout from '../components/layout'
import {  graphql } from 'gatsby'
import Head from '../components/head'
import { BLOCKS, MARKS } from "@contentful/rich-text-types"
import { renderRichText } from "gatsby-source-contentful/rich-text"

export const query = graphql`
    query($slug: String!) {
        contentfulBlogPost(
                slug:  {
                    eq: $slug
                }
            ) {
                title
                slug
                publishedDate(formatString: "MMM Do, YYYY")
                body {
                   raw
                }
            }
    }
`
const Blog = (props) => {
    const Bold = ({ children }) => <span className="bold">{children}</span>
    const Text = ({ children }) => <p className="align-center">{children}</p>

    const options = {
        renderMark: {
            [MARKS.BOLD]: text => <Bold>{text}</Bold>,
          },
        renderNode: {
            [BLOCKS.PARAGRAPH]: (node, children) => <Text>{children}</Text>,
            [BLOCKS.EMBEDDED_ASSET]: node => {
                const alt= node.data.target.fields.title['en-US']
                const url= node.data.target.fields.file['en-US'].url
              return (
                <>
                  <img src={url} alt={alt} />
                
                </>
              )
            },
            [BLOCKS.EMBEDDED_ENTRY]: node => {
                return (
                    <Layout>
                         <Head home_title="Blog Post" />
                          <h1>{props.data.contentfulBlogPost.title}</h1>
                          <p>{props.data.contentfulBlogPost.publishedDate}</p>
                          {props.data.contentfulBlogPost.body}
              
                    </Layout>
                  )
            },
        },
    }
  renderRichText(node.bodyRichText, options)
}

export default Blog 

I have tried to read through from Github but still receiving the same error. I need help.

2 Answers

I would suggest using the following approach:

import { BLOCKS, MARKS } from "@contentful/rich-text-types"
import { renderRichText } from "gatsby-source-contentful/rich-text"
​
const Bold = ({ children }) => <span className="bold">{children}</span>
const Text = ({ children }) => <p className="align-center">{children}</p>
​
const options = {
  renderMark: {
    [MARKS.BOLD]: text => <Bold>{text}</Bold>,
  },
  renderNode: {
    [BLOCKS.PARAGRAPH]: (node, children) => <Text>{children}</Text>,
    [BLOCKS.EMBEDDED_ASSET]: node => {
      return (
        <>
          <h2>Embedded Asset</h2>
          <pre>
            <code>{JSON.stringify(node, null, 2)}</code>
          </pre>
        </>
      )
    },
  },
}
​
renderRichText(node.bodyRichText, options)

Note: extracted from Contentful docs

Using renderRichText built-in method (from gatsby-source-contentful/rich-text). Your code seems a little bit deprecated or at least, some methods.

The following code will never work:

"embedded-asset-block" : (node) => {
    const alt= node.data.target.fields.title['en-US']
    const url= node.data.target.fields.file['en-US'].url
    return <imag src={url} alt={alt} />
}

You need to use the providers of the dependency (@contentful/rich-text-types), that are BLOCKS and MARKS. You can always access to the dependency to check the methods, however, embedded-asset-block stands for BLOCKS.EMBEDDED_ASSET or BLOCKS.EMBEDDED_ENTRY, so:

import { BLOCKS, MARKS } from "@contentful/rich-text-types"
import { renderRichText } from "gatsby-source-contentful/rich-text"
​
const Bold = ({ children }) => <span className="bold">{children}</span>
const Text = ({ children }) => <p className="align-center">{children}</p>
​
const options = {
  renderMark: {
    [MARKS.BOLD]: text => <Bold>{text}</Bold>,
  },
  renderNode: {
    [BLOCKS.PARAGRAPH]: (node, children) => <Text>{children}</Text>,
    [BLOCKS.EMBEDDED_ASSET]: node => {
     // manipulate here your embedded asset
      return (
        <>
          <h2>Embedded Asset</h2>
          <pre>
            <code>{JSON.stringify(node, null, 2)}</code>
          </pre>
        </>
      )
    },
    [BLOCKS.EMBEDDED_ENTRY]: node => {
     // manipulate here your embedded entry
      return (
        <>
        <div>I'm an embedded entry</div>      
        </>
      )
    },
  },
}
​
renderRichText(node.bodyRichText, options)

All you need to do is to add references to the graphql query, unlike with json, the body has raw and references. With that being said, you need to append this to the body after the raw;

references{
           contentful_id
           title
           fixed{
               src
                }
          }

Then you can you id to search for the title and src as you can see above.

import React from 'react'
import Layout from '../components/layout'
import {  graphql } from 'gatsby'
import Head from '../components/head'
import { documentToReactComponents } from '@contentful/rich-text-react-renderer'
import { BLOCKS } from "@contentful/rich-text-types"
export const query = graphql`
    query($slug: String!){
        post: contentfulBlogPost(slug:{eq:$slug}){
            title
            publishedDate(formatString: "MMMM Do, YYYY")
            body{
                raw
                references{
                    contentful_id
                    title
                    fixed{
                        src
                    }
                }
            }
        }
    }
`

const Blog = (props) => {
    const assets = new Map(props.data.post.body.references.map(ref => [ref.contentful_id,ref]))
    const options = {
        renderNode:{
            [BLOCKS.EMBEDDED_ASSET]: node => {
                const url = assets.get(node.data.target.sys.id).fixed.src
                const alt = assets.get(node.data.target.sys.id).title
                return <img alt={alt} src={url}/>
            }
        }
    }
    
    return(
        <Layout>
            
            <h1>{props.data.post.title}</h1>
            <p>{props.data.post.publishedDate}</p>
            {documentToReactComponents(JSON.parse(props.data.post.body.raw),options)}
        </Layout>
    )
}

export default Blog
Related