Rendering Markdown on React

Jul 8, 2020

Markdown (MD) is the Rich Text Format of developers. With it we can put on a screen our ideas in a visual way without having to rely on other, more complicated, markup languages.

I want to explore the process to insert markdown on React components and maybe go further each time.

Limitations of Markdown

First of, let's get this out of the way: Markdown limits itself to text. That means it's only capable of handing headings (h1...h6), text, lists, links and images. Any other elements like main, section, div is out of MD's scope.

Simple rendering

Here, I want to render text like this:

# Hello World!

This is Markdown.

And have it render like this in HTML:

<h1>Hello World!</h1>
<p>This is Markdown.</p>

Fortunately, there's already a few libraries that can do this: react-markdown, react-markdown-renderer and react-mde.

I will use react-markdown for this example.

import React from 'react'
import ReactMarkdown from 'react-markdown'
import './styles.css'

const input = `
# Hello World!

This is Markdown.
`

export default function App() {
  return (
    <div className='App'>
      <ReactMarkdown source={input} />
    </div>
  )
}

And sure enough, that works! Here is the result in Codesandbox.

That was easy. Now for something a little more complicated.

Rendering from a file

Is it as easy as importing the MD file from our JSX file? It is as easy as importing the MD file from our JSX file!

Using react-markdown, for its simplicity, again.

import React from 'react'
import ReactMarkdown from 'react-markdown'
import HelloWorld from './hello-world.md'
import './styles.css'

export default function App() {
  return (
    <div className='App'>
      <ReactMarkdown source={HelloWorld} />
    </div>
  )
}

Here is the demo in Codesandbox again. It is even neater than the previous example. Now, something even more difficult.

Rendering multiple files

Finally, I want to read multiple files at the same time and output their render as before.

In addition, I want to be able to output each file on it's own with a link.

This is done with a router. Fortunately, a react-router exists! Even more so, I'm able to achieve what I wanted just reading at the first example in the page, which is excellent news. Also, Node has a module called fs that allows to read the file system, in this case our app environment. And it's as easy as a couple of lines. Assuming I have put my file on a a /post folder, I can execute this code.

const path = './src/posts/' // path for the MD files folder within our project
const fs = require('fs')
const fileList = fs.readdirSync('./src/posts') // creates an array of filenames

Then, I want to read the data contents, so I create a function for this.

function getData(file) {
  const data = fs.readFileSync(file, 'utf8')
  return data ? data : null
}

This function takes a file and returns its content as a string. I have to declare encoding ("utf-8") or else I get an array of number, which is the characters values in ASCII.

Now I can create my app.

export default function App() {
  return (
    <div className='App'>
      <Router>
        <ul>
          {fileList.map((post) => (
            <li>
              <Link to={`/${post}`}>{post}</Link>
            </li>
          ))}
        </ul>

        <Switch>
          {fileList.map((post) => (
            <Route path={`/${post}`}>
              <ReactMarkdown source={getData(`${path}${post}`)} />
            </Route>
          ))}
        </Switch>
      </Router>
    </div>
  )
}

I have surround all my code on the <Router> component for it to work and create separate .map functions for the link list and the actual routing. The <Switch> component takes care of rendering the text formatted from markdown

The result, in Codesandbox, is a bit hacky since it uses the file name as the link text. But I'm satisfied that I could get the result I was looking for with relative ease. All I'm left with is to explore how to read certain lines from a file to extract the title and fix the link text.