Search code examples
reactjsmarkdownnext.jsremarkjschakra-ui

How to use custom components in react-markdown


Context: I have a Next.js site with Chakra UI. I have some user provided markdown content that is fetched from an external source (say, GitHub README.md for a repo) at runtime.

Now, by default, react-markdown (based on remarkjs) uses the HTML <img> tag for markdown images (![]()). I want to use the new <Image /> component released in Next.js 10 in the user provided markdown. Additionally, I also would like to replace other tags with corresponding Chakra UI components.

How do I go about doing this?

SOLUTION

// utils/parser.tsx

import Image from 'next/image';

export default function ImageRenderer({ src, alt }) {
  return <Image src={src} alt={alt} unsized />;
}

and then in the required page:

//pages/readme.tsx

import ReactMarkdown from 'react-markdown';
import imageRenderer from '../utils/parser';

// `readme` is sanitised markdown that comes from getServerSideProps
export default function Module({ readme }) {
  return <ReactMarkdown allowDangerousHtml={true} renderers={{ image: imageRenderer }} children={readme} />
}

same for other elements...


Solution

  • react-markdown lets you define your own renderers. I have recently did something similar. I want to use figure and figurecaption elements. So, I have created my own image renderer react component.

    Component

    export default function ImageRenderer(props) {
        const imageSrc = props.src;
        const altText = props.alt;
        return (
            <figure className="wp-block-image size-large is-resized">
                <img
                    data-loading="lazy" 
                    data-orig-file={imageSrc}
                    data-orig-size="1248,533"
                    data-comments-opened="1"
                    data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}"
                    data-image-title="builtin_vs_dotnetwarp"
                    data-image-description=""
                    data-medium-file={imageSrc + "?w=300"}
                    data-large-file={imageSrc + "?w=750"}
                    src={imageSrc + "?w=10241"}
                    alt={altText}
                    srcSet={imageSrc + "?w=1024 1024w, " + imageSrc + "?w=705 705w, " + imageSrc + "?w=150 150w, " + imageSrc + "?w=300 300w, " + imageSrc + "?w=768 768w, " + imageSrc + "?1248w"}
                    sizes="(max-width: 707px) 100vw, 707px" />
                <figcaption style={{ textAlign: "center" }}>{altText}</figcaption>
            </figure>
        );
    }
    

    And I use that renderer as below

    <ReactMarkdown source={blogResponse.data.content} escapeHtml={false} renderers={{ "code": CodeBlockRenderer, "image": ImageRenderer }} />
    

    renderers={{ "code": CodeBlockRenderer, "image": ImageRenderer }} is where you mention custom renderers.