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

Top comments (2)

Collapse
 
mirianfsilva profile image
MΓ­rian

Thanks for this package, help me a lot!

Collapse
 
fifo profile image
Filipe Herculano

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

5 Website To Learn Frontend Web Development Faster

In this article, Bentil Shadrack has curated 5 resourceful sites that will help you better you web development skills really fast.