Rendering Markdown in React without react-markdown
Written by Frank Fiegel on .
- Dangerously set inner HTML
- Rehype and Remark
- React hook to render markdown
- react-markdown vs a custom React hook
There appear to be literally a thousand ways to render markdown in React.
Up to now, I've been using react-markdown and I was happy with it.
However, I ran into an issue integrating KaTex and react-markdown did not make it easy to troubleshoot.
Dangerously set inner HTML
The main reason I chose to use react-markdown
initially was that it advertised it did not rely on dangerouslySetInnerHTML
.
It turns out that the same can be achieved by using rehype-react
, which is a plugin that transforms HTML into React components.
Rehype and Remark
To understand the Markdown-to-React conversion process, it's crucial to grasp the roles of rehype
and remark
.
-
remark
- Markdown processor that parses Markdown text into mdast (Markdown Abstract Syntax Tree) format.- Key features:
- Parses Markdown into a structured AST
- Offers a plugin system for custom Markdown processing
- Supports various Markdown flavors and extensions
- Key features:
-
rehype
- HTML processor that works with HAST (HTML Abstract Syntax Tree).- Key features:
- Processes HTML ASTs
- Provides plugins for HTML modifications
- Facilitates the conversion of HTML to other formats
- Key features:
These tools work together in a pipeline to convert Markdown into React elements:
remark
parses Markdown into mdastremark
plugins can modify the mdast- The mdast is converted to HAST (HTML AST)
rehype
plugins can modify the HAST- Finally, the HAST is converted to React elements
React hook to render markdown
Upon becoming aware of the existence of rehype-react
, I started to implement a React hook that would allow me to render Markdown without using higher-level abstractions like react-markdown
.
This is what I came up with:
This code is heavily inspired by react-remark
. The latter basically does the same thing, but depends on older versions of remark
and rehype
.
Here is how it works:
-
Parse Markdown using
remark-parse
- Converts Markdown string to an Abstract Syntax Tree (AST)
- Example: "# Hello" → heading node with text "Hello"
-
Apply remark plugins
- Transforms Markdown AST (e.g., add syntax highlighting)
-
Transforms Markdown AST to HTML AST using
remark-rehype
- Example: heading node →
<h1>
element
- Example: heading node →
-
Apply rehype plugins
- Modifies HTML AST (e.g., add attributes to links)
-
Convert to React elements using
rehype-react
- Transforms HTML AST to React elements
- Example:
<h1>
element →React.createElement('h1', null, 'Hello')
react-markdown
vs a custom React hook
I am not aware of any major differences between react-markdown
and the custom React hook I suggested.
Ultimately, even if the two implementations are equivalent, I prefer the custom React hook because it allows me a direct control over the conversion process.
The simplicity behind the custom React hook was a surprise to me and what prompted to write this post.