Edit (22 Nov 2017): Major post restructure. Simplified things a lot by removing a redundant step. Also changed the example slightly
Edit (23 Jul 2019): Javascript, and this website, have changed enough that this post is no longer relevant. If you want to read it, it is still here. However, there are better ways of acomplishing these goals now (MDX is probably the best bet right now). As such, the examples on this page will be broken
Since a fair bit has changed in my apporach since the last post, I thought I'd go through and rewrite the entire thing.
The Markdown specification allows you to write HTML inside the Markdown file, and that HTML will be rendered in the final page. However, this is static HTML, with no interactivity. This post shows a way to include React elements in Markdown, and will most likely work for other systems too.
// MdRenderer.jsx
import React from "react";
import unified from "unified";
import remarkParse from "remark-parse";
import remarkRehype from "remark-rehype";
import rehypeReact from "rehype-react";
const components = {
// A mapping of tag names (lower case) to Components
// e.g.
mycomponent: MyComponent
};
const processor = unified()
.use(remarkParse)
// Place any remark plugins here
.use(remarkRehype, { allowDangerousHTML: true })
// Place any rehype plugins here
.use(rehypeReact, { createElement });
function createElement(component, props, children) {
const Tag =
(components && component && components[component]) || component || "div";
return <Tag {...props}>{children}</Tag>;
}
export default function MdRenderer({ text }) {
return (
<div className="MdRenderer">{processor.processSync(text).contents}</div>
);
}
It's extremely simple to use in Markdown:
This is a **Markdown** file
<MyComponent propName="prop value"></MyComponent>
There are three things to keep in mind:
</MyComponent>
in this case).
Custom void tags (e.g. <MyComponent propName="..." />
) are not recognised properly by rehype, and will cause DOM nesting errors.
Valid HTML void tags (like <input>
and <img>
) still work.I use unified and its syntaxes remark and rehype to parse the Markdown. There is a large number of plugins available for this ecosystem, allowing you to add more features in (like emoji 👍).
import remarkParse from "remark-parse";
import remarkRehype from "remark-rehype";
import rehypeReact from "rehype-react";
// ...
const processor = unified()
.use(remarkParse)
// Place any remark plugins here
.use(remarkRehype, { allowDangerousHTML: true })
// Place any rehype plugins here
.use(rehypeReact, { createElement });
Using unified is simple enough, just use()
any plugins you need. These three are neccesary, so make sure they're included.
The rehype-react plugin converts the output from the rehype plugins into fully rendered React elements. It does this using React's createElement function. However, it does not know about your custom components. In order to actually be able to use custom components, we need to override the createElement function to allow the components.
import React from "react";
// ...
function createElement(component, props, children) {
const Tag =
(components && component && components[component]) || // Get component from map if present
component || // Otherwise just the string
"div"; // Default to div
// And return the formed component
return <Tag {...props}>{children}</Tag>;
}
This is just a small pure functional component that takes a text
property and returns the fully rendered page.
export default function MdRenderer({ text }) {
return (
<div className="MdRenderer">{processor.processSync(text).contents}</div>
);
}
And that's it; React in Markdown. This method works with server-side rendering/pre-rendering too, making it useful for nearly all purposes.
This post you're reading is using the very code shown here. This post doesn't use any of the custom Components I use on this website, so I thought I'd include an example at the bottom.
All you need to do is write out your element.
<Expandable>
<LargeLink href="/posts/react-in-markdown-updated/" title="This Post" />
</Expandable>
Just like you would in JSX
Becomes:
All you need to do is write out your element.
Just like you would in JSX