DEV Community

Filipe Herculano
Filipe Herculano

Posted on

Creating a custom react hook for markdown parsing

I am making a side project in React that requires markdown parsing so I decided to use that as a good candidate to experiment with custom hooks

Checkout this fantastic post from Amelia Wattenberger that goes over a comparison between traditional class components versus using hooks and how they make React feel less bloated and more natural to work with

Here's what I needed to do:

  • Parse a markdown string
  • Sanitize that string to prevent XSS attacks

Apparently there is a vast number of parsers out there. I decided to go with marked which seems like a good library with an active community and a nice and simple implementation

Again, the same could be said for sanitizing html (for some reason people just like writing parsers a lot), so I picked sanitize-html which offers a nice level of configuration through a simple object

Setup

Alright let's get to work

// parsing markdown with marked
const marked = require('marked')
const md = `
  # heading

  [link][1]

  [1]: #heading "heading"`

const tokens = marked.lexer(md)
const html = marked.parser(tokens)
Enter fullscreen mode Exit fullscreen mode

Will output this html!

<h1 id="heading">heading</h1>
<p><a href="#heading" title="heading">link</a></p>
Enter fullscreen mode Exit fullscreen mode

Now, to prevent XSS, let's add this before using the html

// sanitizing raw html with sanitize-html
const sanitizeHtml = require('sanitize-html')
// passing the html output from marked
const clean = sanitizeHtml(html)
Enter fullscreen mode Exit fullscreen mode

Output now is

heading
<p><a href="#heading" title="heading">link</a></p>
Enter fullscreen mode Exit fullscreen mode

Wait, what? Where's our h1 tag? Well, apparently the default options for sanitize-html consider h1 unsafe (I guess), they go over the specs in their README so I went and added my custom defaults

Marked also supports a nice set of configurations (syntax highlighting being my favourite) you can checkout their docs here

useMarked('# yay!')

Awesome, we have everything, let's turn that into a React hook called useMarked

import { useState, useEffect } from 'react'
import sanitizeHTML from 'sanitize-html'
import marked from 'marked'

import defaultOptions from './defaultOptions'

export const useMarked = (markdown, options = defaultOptions) => {
  const [html, setHtml] = useState(markdown)

  useEffect(() => {
    if (options.markedOptions) {
      marked.setOptions(options.markedOptions)
    }
    const tokens = marked.lexer(markdown)
    const html = marked.parser(tokens)
    setHtml(
      options.skipSanitize ? html : sanitizeHTML(html, options.sanitizeOptions)
    )
  }, [markdown])

  return html
}
Enter fullscreen mode Exit fullscreen mode

And now we can use it in any function component by doing

import React from 'react'
import { useMarked } from 'use-marked-hook'

const App = () => {
  const markdown = `**bold content**`
  const html = useMarked(markdown)
  // html -> <p></strong>bold content</strong></p>
  return <div dangerouslySetInnerHTML={{ __html: html }} />
}
Enter fullscreen mode Exit fullscreen mode

Testing Custom Hooks

I also found that there's a quick way to test your hooks using the @testing-library/react-hooks package which provide us with the nice renderHook helper

Testing our useMarked hook looks like this

import { useMarked } from 'use-marked-hook'
import { renderHook } from '@testing-library/react-hooks'

describe('useMarked', () => {
  it('Receives markdown and returns html', () => {
    const { result } = renderHook(() => useMarked('# test'))
    expect(result.current).toBe('<h1>test</h1>\n')
  })
})
Enter fullscreen mode Exit fullscreen mode

⚠️ Note the newline character added at the end of the output (jest errors were very unhelpful in seeing that and it took me quite a bit to realize tests were failing because of it 🤦‍♂️)

Conclusion

To save you some effort, if you ever find the need for a markdown parser in your react projects, I published this custom hook as an npm package which you can download and use now 😉

yarn add use-marked-hook
Enter fullscreen mode Exit fullscreen mode

I made the code for it available on github

It also includes a sample react app that uses useMarked hook to render a local markdown file into an html page that is later published live through github pages, checkout the result here

Discussion (2)

Collapse
mirianfsilva profile image
Mírian

Thanks for this package, help me a lot!

Collapse
fifo profile image
Filipe Herculano Author

hey! — you are very welcome, I'm glad someone found it useful :)