coreyward/react-portable-text

Unable to Render Code blocks

mrrogercampbell opened this issue · 11 comments

@coreyward I am attempting to render a code block and have passed in a code prop to the serializer but keep receiving the following error: Error in function BlockSerializer in ./node_modules/react-portable-text/node_modules/@sanity/block-content-to-hyperscript/lib/serializers.js:23 and Unknown block type "code", please specify a serializer for it in the serializers.types prop

My Code:

// Component Rendering Portable Text
 <PortableText
        content={content._rawPortableBody}
        serializers={serializers}
      />
// serializers.js
import React from "react";
import imageUrlBuilder from '@sanity/image-url'

let baseHeaderStyles = 'py-5 px-48'
let baseParaStyles = 'py-2 px-48'
let baseListStyles = 'list-inside px-52'

const urlFor = source => imageUrlBuilder({ projectId: '', dataset: 'production' }).image(source)

const serializers = {
    code: props => <pre>{JSON.stringify(props, null, 2)}</pre>,
    h1: props => <h1 className={baseHeaderStyles} >{props} </h1>,
    h2: props => <h2 className={`${baseHeaderStyles} text-3xl`} {...props} />,
    h3: props => <h3 className={`${baseHeaderStyles} text-2xl`} {...props} />,
    normal: props => <p className={baseParaStyles} {...props} />,
    ul: ({ children }) => <ul className={`${baseListStyles} list-disc`}>{children}</ul>,
    ol: ({ children }) => <ol className={`${baseListStyles} list-decimal`}>{children}</ol>,
    li: ({ children }) => <li>{children}</li>,
    imageSection: props => (
        <figure>
            <img src={urlFor(props.image.asset)} alt={props.alt} />
            {/* {JSON.stringify(props, null, 2)} */}
        </figure>
    )
    // someCustomType: YourComponent,
}


export default serializers;

As you can see I have a code prop created inside my serializer file but for some reason, the error keeps being thrown. Any assistance you could provide would be great. Thanks/

Also have tried adding a types prop to the serializers object:

const serializers = {
    types: {
        code: props => <pre>{JSON.stringify(props, null, 2)}</pre>,
    },
    h1: props => <h1 className={baseHeaderStyles} >{props} </h1>,
    h2: props => <h2 className={`${baseHeaderStyles} text-3xl`} {...props} />,
    h3: props => <h3 className={`${baseHeaderStyles} text-2xl`} {...props} />,
    normal: props => <p className={baseParaStyles} {...props} />,
    ul: ({ children }) => <ul className={`${baseListStyles} list-disc`}>{children}</ul>,
    ol: ({ children }) => <ol className={`${baseListStyles} list-decimal`}>{children}</ol>,
    li: ({ children }) => <li>{children}</li>,
    imageSection: props => (
        <figure>
            <img src={urlFor(props.image.asset)} alt={props.alt} />
            {/* {JSON.stringify(props, null, 2)} */}
        </figure>
    ),
    // someCustomType: YourComponent,
}

Since code is one of the default mark types, your block type is conflicting with it. This is a fairly rare scenario where you want to use the context of a node to determine the rendering intent (i.e. if it's a mark, render it one way, and if it's a block, render it another way). Since react-portable-text is designed to stop worrying about distinguishing the two for the sake of lower cognitive overhead, dealing with this scenario is a bit trickier. You may be able to replace the span serializer, but it is likely easier to use @sanity/block-content-to-react directly for this case if you aren't able to use a unique type name for the block type in Sanity.

Going to close this out since it's been a couple weeks, but if you still need help feel free to reply!

@coreyward I have tried using PortableText without any serializer for the code block, it says
Unknown block type "code", please specify a serializer for it in the serializers.types prop
So wether I provide a serializer or don't it simply does not render out the code block...I don't wish to use other library because of the ease this library provides to define custom blocks, just stuck on this CODE thing rendering, any help will be appreciated...

@trulymittal Are you able to use a different name for your custom code block in Sanity? E.g. codeBlock. If so, you can change to that and avoid this issue altogether.

@coreyward yeah I can easily do that in sanity but I don't see any reason for that, it seems like a hack for that. Isn't it possible to fix without any change of names of code block ?

It's not a hack. You're using the same name twice in Sanity. One is a built in type, so almost like a reserved keyword, only Sanity lets you change it. The other is your own code. This is typically a bad idea, and with TS types and GraphQL you may run into other issues.

This said, you can continue without changing this. The formal way to differentiate between these two types is via marks and blocks. This library abstracts away marks and blocks for simplicity because doing so is much easier and almost never necessary. So you'll need to instead use one of the lower level libraries to author serializers.

@coreyward ooooo, I got this, I thought defining a serialiser for the code block would override both of them, but my bad, thanks for the clarification, you rock 🫡

@trulymittal What was your solution to this ? I tried to change it in sanity and now getting an error with following

Error: Unknown block type "codeBlock", please specify a serializer for it in the `serializers.types` prop

This error happened while generating the page. Any console logs will be displayed in the terminal window.

@coreyward If you have any example with Code Block, i can take a look at it.

@SandeepKumarYaramchitti or anyone else having this issue, this is the solution that I used and is suggested by @coreyward, works flawlessly...

Sanity schema looks like, just note that for type code i have given it a name codeBlock, which can be any name, and then you need to reference that name for serializing in Portable text renderer

{
  name: 'content',
  title: 'Content',
  type: 'array',
  of: [
    {
      type: 'block',
    },
    {
      type: 'image',
    },
    {
      type: 'code',
      name: 'codeBlock',
    },
  ],
},

Serializer looks like this:

<PortableText
  className='prose mx-auto mt-8'
  content={content}
  projectId={process.env.PROJECT_ID}
  dataset={process.env.DATASET}
  serializers={{
    code: props => {
      // console.log('Testing', props)
      return <pre>{props.children}</pre>
    },
    codeBlock: props => {
      return <CodeBlock code={props.code} language={props.language} />
    },
  }}
/>

Code block component can be anyway you like, a pre tag also, but I have used syntax highlighter like this:

import SyntaxHighlighter from 'react-syntax-highlighter'
import vs2015 from 'react-syntax-highlighter/dist/esm/styles/hljs/vs2015'

export default function CodeBlock({ code, language }) {

  return (
    <SyntaxHighlighter
      language={language}
      style={vs2015}
      showLineNumbers={language !== 'sh'}
      customStyle={{
        marginTop: 0,
        borderTopLeftRadius: 0,
        borderTopRightRadius: 0,
      }}
      wrapLongLines
    >
      {code}
    </SyntaxHighlighter>
  )
}

Thank you for the solution!