05-03-2023

How to use react markdown with code highlighting

#markdown #react #syntax-highlighting


When it comes to writing content for the web, you’ll eventually come across Markdown. To safely convert my Markdown articles into HTML and React element for my blog, I used the react-markdown library. However, I found the library's documentation a bit difficult to navigate, so I created a guide on how to use it in combination with react-syntax-highlighter to write articles that feature beautifully embedded code blocks, just like the ones we often see in development blogs.

What is Markdown?

Markdown is a lightweight markup language that uses plain text syntax to indicate formatting. It is a simple way to write formatted text that can be easily converted to HTML to be used on the web.

Markdown allows you to create headings, lists, emphasis, links, images, and more by using simple symbols and characters. For example, to create a heading, you simply add one or more hash symbols (#) at the beginning of a line, followed by the heading text:

# This is a top-level heading ## This is a second-level heading

When this Markdown is rendered to HTML, it will generate the following markup:

<h1>This is a top-level heading</h1> <h2>This is a second-level heading</h2>

How do we render Markdown text into HTML in a react application?

So far, I’ve created blog post in Markdown syntax which are stored as string in a database. But how to actually render it into HTML in my react application? The react-markdown library does the magic!

With react-markdown, you can simply pass your Markdown content as a string to the ReactMarkdown component, and it will automatically generate the corresponding HTML output.

First install the package with your preferred package manager:

npm i react-markdown

Now, let’s see a basic example:

import ReactMarkdown from 'react-markdown'; function App() { return ( <div className="App"> <ReactMarkdown># Hello, *world*!</ReactMarkdown> </div> ); }

The resulting output will be:

<h1> Hello, <em>world</em>! </h1>

We can also pass the markdown text as children prop to the ReactMarkdown component. It represents the Markdown text you want to render:

import ReactMarkdown from 'react-markdown'; function App() { const markdown = `# Hello, World! This is a **bold** text. `; return ( <div className="App"> <ReactMarkdown children={markdownText} /> </div> ); }

Customize :)

So ReactMarkdown generates standard HTML tags for each Markdown element and applies some basic styles. What makes the library really useful though is that you can customize how the elements are rendered.

Step 1: Let’s create our custom components and style it according to our design, so for example:

export const ArticleLink = ({ href, children }: any) => { return ( <a href={href} target="_blank" rel="noreferrer" className="font-lato font-medium text-base text-violet-secondary no-underline decoration-violet-secondary mb-4 sm:text-lg sm:mb-6 " > {children} </a> ); };

Check the documentation to see which props to use for which HTML elements.

Step 2: We want to pass these custom components to the ReactMarkdown component, so that it knows how to render certain HTML tags.

Let's create a MarkdownRenderer component, so that we can reuse it wherever we need in our app. It receives the markdownText as prop and renders the ReactMarkdown component:

import ReactMarkdown from "react-markdown"; import { ArticleH2 } from "./ArticleH2"; import { ArticleH3 } from "./ArticleH3"; import { ArticleImage } from "./ArticleImage"; import { ArticleLink } from "./ArticleLink"; import { ArticleParagraph } from "./ArticleParagraph"; import { ArticleCode } from "./ArticleCode"; export const MarkdownRenderer = ({ markdownText }: any) => { // eslint-disable-next-line react/no-children-prop return ( <ReactMarkdown children={markdownText} components={{ img: ArticleImage, h2: ArticleH2, h3: ArticleH3, p: ArticleParagraph, a: ArticleLink, }} ></ReactMarkdown> ); };

Let's break the code down:

The ReactMarkdown component accepts a prop called components: It takes in an object that maps HTML tag names to custom React components.

So it is basically saying:

Hey, if you ecounter an img element in the Markdown text, render the ArticleImage component, if you encounter any h2, then render ArticleH2 ... and so on.

So the key/property represents HTML equivalents for the things you write with markdown (such as h1 for # heading ) and as the value you pass your custom components.

Step 3: Use the MarkdownRenderer where you need. In my case I use Next.js and getStaticProps to fetch my markdown content from my API:

import ArticleHeader from "@/components/ArticleHeader"; import Button from "@/components/Button"; import { MarkdownRenderer } from "@/features/markdown/MarkdownRenderer"; import { GetStaticPaths, GetStaticPropsContext } from "next"; const backendURL = "http://localhost:5005"; export default function Article({ article }: any) { return ( <div className="w-full bg-violet-bg"> <div className="mx-auto px-5 max-w-[750px] pb-24 sm:px-9"> <div className="flex flex-row justify-end pt-8"> <Button href="/">Go Back</Button> </div> <ArticleHeader title={article.title} date={article.release_date} keywords={article.keywords} /> <MarkdownRenderer markdownText={article.text}></MarkdownRenderer> </div> </div> ); } export async function getStaticProps(context: GetStaticPropsContext) { const response = await fetch( `${backendURL}/api/articles/${context.params?.id}` ); const data = await response.json(); return { props: { article: data, }, revalidate: 60 * 60, }; } export const getStaticPaths: GetStaticPaths = async () => { const response = await fetch(`${backendURL}/api/articles`); const data = await response.json(); const paths = data.map((item: any) => { return { params: { id: `${item.id}`, title: item.title.split(" ").join("-") }, // we define the params for which next js should build this page at build time }; }); console.log(paths); return { paths, fallback: "blocking", }; };

Et voilà! The page you are reading right now is the result of using the MarkdownRenderer. Well, almost. Just one thing is missing: the highlighted code.

Make Your Code Pop: Using Syntax Highlighter in React

react-markdown will display code in a very plain format, without all the fancy colors we are used to see in our favorite code editor.

For that, we can use another awesome library called react-syntax-highlighter together with react-markdown.

Step 1: install the package

$ npm install react-syntax-highlighter

Step 2: Let’s create a separate component which will be responsible for rendering syntax-highlighted code blocks. Before we define the component, let’s first import the necessary packages and styles in here.

import { PrismLight as SyntaxHighlighter } from "react-syntax-highlighter"; import tsx from "react-syntax-highlighter/dist/cjs/languages/prism/tsx"; import typescript from "react-syntax-highlighter/dist/cjs/languages/prism/typescript"; import { nightOwl } from "react-syntax-highlighter/dist/cjs/styles/prism";
  • There are two options to choose from: Higlight.js and Prism.js. If you want to use JSX or TSX, you have to use Prism. Here, I’ve used the PrismLight version, which is a lightweight version that only includes the languages and styles that are used in our application. With that we need to also manually import the languages we want to use and highlight.
  • Then you can choose which style you want to use, you can preview them here

Step 3: Register the specific languages you’ve chosen:

SyntaxHighlighter.registerLanguage("tsx", tsx); SyntaxHighlighter.registerLanguage("typescript", typescript);

Step 4: Now we can define our custom ArticleCode component that uses the SyntaxHighlighter component to render code blocks with syntax highlighting.

export const ArticleCode = ({ inline, className, children, ...props }: any) => { // Extract the language from the className using a regular expression const match = /language-(\w+)/.exec(className || ""); // Render a code block with syntax highlighting if inline is false return !inline ? ( <SyntaxHighlighter children={String(children).replace(/\n$/, "")} style={nightOwl} customStyle={{ borderRadius: "5px", backgroundColor: "#08090a", marginBottom: "1.25rem", padding: "1rem", }} language={match?.[1] || "typescript"} PreTag="div" {...props} /> ) : ( <code className={` bg-gray-200 rounded-sm font-normal ${className} `} {...props} > {children} </code> ); };

The component takes several props, including:

  • children - the code that should be highlighted
  • The language is specified in the className prop using the format language-<language>, where **<language>**is the name of the language being used (e.g. language-typescript) ( see https://www.npmjs.com/package/react-markdown?activeTab=readme#appendix-b-components)
  • The inline prop is a boolean that determines whether the code block should be rendered inline or as a block

Let’s break the code down:

  1. We detect the language from the className prop using a regular expression.
  2. If inline is false, we render a SyntaxHighlighter component. If true, the component renders a code block without syntax highlighting.
  3. We pass the needed props to the SyntaxHighlighter. Additionaly I used the customStyle prop to customize the code block to my taste and my design. I also set the default language to typescript.

Step 5: Finally, we can pass our ArticleCode component to our MarkdownRenderer so that it knows how to render code blocks.

/* eslint-disable react/no-children-prop */ import ReactMarkdown from "react-markdown"; import { ArticleH2 } from "./ArticleH2"; import { ArticleH3 } from "./ArticleH3"; import { ArticleImage } from "./ArticleImage"; import { ArticleLink } from "./ArticleLink"; import { ArticleParagraph } from "./ArticleParagraph"; import { ArticleCode } from "./ArticleCode"; export const MarkdownRenderer = ({ markdownText }: any) => { // eslint-disable-next-line react/no-children-prop return ( <ReactMarkdown children={markdownText} components={{ img: ArticleImage, h2: ArticleH2, h3: ArticleH3, p: ArticleParagraph, a: ArticleLink, code: ArticleCode, }} ></ReactMarkdown> ); };

Done! With these libraries combined, you'll be able to create stunning articles with beautifully embedded code blocks - just like the pros. What I really like about this duo is the way you can customize it to fit the style of your website. So, let's start creating amazing content! :)

Resources

react-markdown documentation
react-syntax-highlighter documentation

elifscode

© 2023 designed & built by Elif Gömleksiz