DEV Community

Filipe Herculano
Filipe Herculano

Posted on

7 1

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

11 Tips That Make You a Better Typescript Programmer

typescript

1 Think in {Set}

Type is an everyday concept to programmers, but it’s surprisingly difficult to define it succinctly. I find it helpful to use Set as a conceptual model instead.

#2 Understand declared type and narrowed type

One extremely powerful typescript feature is automatic type narrowing based on control flow. This means a variable has two types associated with it at any specific point of code location: a declaration type and a narrowed type.

#3 Use discriminated union instead of optional fields

...

Read the whole post now!

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay