useMarked hook

Filipe Herculano, February 11, 2020

random image from unsplash

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


Alright let's get to work

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


  [1]: #heading "heading"

const tokens = marked.lexer(md)
const html = marked.parser(tokens)

Will output this html!

<h1 id="heading">heading</h1>
<p><a href="#heading" title="heading">link</a></p>

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)

Output now is

<p><a href="#heading" title="heading">link</a></p>

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 which looks like this

View defaults
  "allowedTags": [
  "disallowedTagsMode": "discard",
  "allowedAttributes": {
    "a": ["href", "name", "target"],
    "img": ["src"]
  "selfClosing": [
  "allowedSchemes": ["http", "https", "ftp", "mailto"],
  "allowedSchemesByTag": {},
  "allowedSchemesAppliedToAttributes": ["href", "src", "cite"],
  "allowProtocolRelative": true

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) {
    const tokens = marked.lexer(markdown)
    const html = marked.parser(tokens)
      options.skipSanitize ? html : sanitizeHTML(html, options.sanitizeOptions)
  }, [markdown])

  return html

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 }} />

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'))

⚠️ 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 🤦‍♂️)


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

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