3

Problem

Using React-Markdown I can fully use my custom built components. But this is with specific pre-built keywords in the markdown. Like paragraph or images. That works PERFECTLY. But the problem is that these seem to all be pre-built words/conditions like paragraphs, headers, or images.

I can't find a way to add something new key word in my markdown like "CustomComponent" to be used. That's all I need at this point ><

This works just fine for me to make the markdown's image into a custom "footer" component I made elsewhere. I know it's ridiculous but it works. But I have no idea how to make this renderer accept/create a new keyword like "emoji" or "customComponent" or "somethingSilly".

let body = 
    `![Fullstack React](https://dzxbosgk90qga.cloudfront.net/fit-in/504x658/n/20190131015240478_fullstack-react-cover-medium%402x.png)`;

const renderers = {
    image: () => <Footer/>
};

<ReactMarkdown source={body} renderers={renderers} />;

Some past work I did:

Some documentation: https://reposhub.com/react/miscellaneous/rexxars-react-markdown.html https://github.com/rexxars/commonmark-react-renderer/blob/master/src/commonmark-react-renderer.js#L50

Examples: https://codesandbox.io/s/react-markdown-with-custom-renderers-961l3?from-embed=&file=/src/App.js

But nothing indicates how I can use "CustomComponent" to indicate to inject a custom component.

Use Case / Background

I'm trying to retrieve an article from my database that is formatted like so in markdown (basically a giant string). I'm using regular react with typescript and redux-- this is the only portion of my application that needs this.

"
# Title

## Here is a subtitle

Some text

<CustomComponentIMade/>

Even more text after.


<CustomComponentIMade/>

"
NeuralCode
  • 135
  • 1
  • 2
  • 13

2 Answers2

5

I know its most likely a little late for your purposes, but I've managed to solve this issue using a custom remark component.

Essentially you'll need to use the remark-directive plugin as well as a small custom remark plugin (I got this plugin straight from the remark-directive docs)

Then in react markdown you can specify the plugins, custom renderers and custom tags for eg.

import React from 'react'
import ReactMarkdown from 'react-markdown'
import {render} from 'react-dom'
import directive from 'remark-directive'
import { MyCustomComponent } from './MyCustomComponent'
import { visit } from "unist-util-visit" 
import { h } from "hastscript/html.js"

// react markdown components list
const components = {
  image: () => <Footer/>,
  myTag: MyCustomComponent
}

// remark plugin to add a custom tag to the AST
function htmlDirectives() {
  return transform

  function transform(tree) {
    visit(tree, ['textDirective', 'leafDirective', 'containerDirective'], ondirective)
  }

  function ondirective(node) {
    var data = node.data || (node.data = {})
    var hast = h(node.name, node.attributes)

    data.hName = hast.tagname
    data.hProperties = hast.properties
  }
}

render(
  <ReactMarkdown components={components} remarkPlugins={[directive, htmlDirectives]}>
    Some markdown with a :myTag[custom directive]{title="My custom tag"}
  </ReactMarkdown>,
  document.body
)

So in your markdown wherever you have something like :myTag[...]{...attributes} you should render the MyCustomComponent with attributes as props.

Sorry I haven't tested the code, but hopefully it communicates the gist of things, if you need a working example let me know and I'll do my best to set one up.

0

I have tried this way and it worked for me

import CustomNextImage from '@commonComponentsDependent/CustomNextImage';
import dynamic from 'next/dynamic';
import Link from 'next/link';
import { FC } from 'react';
import ReactMarkdown from 'react-markdown';
import { Options } from 'react-markdown/lib/ast-to-react';
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
import remarkGfm from 'remark-gfm';

const SyntaxHighlighterDynamic = dynamic(() => import('./SyntaxHighlighter'));

import classes from './styles.module.css';

interface Props {
    content: string;
}

type TCustomComponents = Options['components'];
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

const MdToHTML: FC<Props> = ({ content }) => {
    const customComponents: TCustomComponents = {
    // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        img(image) {
            if (!image.src) return <></>;

            return (
                <div className={classes['img-container']}>
                    <CustomNextImage
                        src={image.src}
                        alt={image.alt}
                    />
                </div>
            );
        },

        a({ href, children, node }) {
            if (!href) return <></>;

            if (
                href.startsWith('/') ||
                href.startsWith('https://lognmaze.com')
            ) {
                return (
                    <Link href={href} prefetch={false} passHref>
                        <a>{children}</a>
                    </Link>
                );
            }

            return (
                <a
                    href={href}
                    target='_blank'
                    rel='noopener noreferrer'
                >
                    {children}
                </a>
            );
        },

        code({ node, inline, className, children, ...props }) {
            const match = /language-(\w+)/.exec(className || '');

            return !inline && match ? (
                <SyntaxHighlighterDynamic language={match[1]} PreTag='div' {...props}>
                    {String(children).replace(/\n$/, '')}
                </SyntaxHighlighterDynamic>
            ) : (
                <code className={className} {...props} data-code-inline='true'>
                    {children}
                </code>
            );
        },
    };

    return (
        <ReactMarkdown
            components={customComponents}
            remarkPlugins={[remarkGfm]}
        >
            {content}
        </ReactMarkdown>
    );
};

export default MdToHTML;
Dream Echo
  • 165
  • 2
  • 8