Tricks of the Trade: Gatsby + HTML Transforms

January 23, 2020


I wanted to leverage Gatsby's <Link> component from markdown source, a lightweight HTML string to React element conversion library helps us achieve just that.

Overview

I help develop and maintain my girlfriend's online fitness program website over at KristieLengel.com. It's a Gatsby generated site, hosted on Netlify, using Firebase as a backend. She uses Netlify CMS to manage the content through different forms (but uses Markdown under the hood) which works out pretty nicely (although, it can be buggy at times, I probably need to update the packages).

The Problem

We wanted to make members' quality of life better by reducing some of the navigation they may have encountered when viewing a workout routine. The workout routine is rather simple, instructions of exercises, repetitions or interval timing formatted on the page however the text was provided via the CMS (which is a user friendly markdown editor). Each exercise or warm-up routine has a video associated with it so a member can get an idea of what s/he should do if unfamiliar with the movement.

At first implementation, the exercise names were just relative links to the exercise library within the site. You could click the exercise name (navigating away from the workout you're trying to do), read the title and description and/or play the video demonstration. Then the user has to press back and so forth.

To make this a better experience, if the user clicked on an exercise name (or any relative link that doesn't leave the site in this case), we wanted to overlay the video on top of the workout routine. The only issue being my Gatsby template just passes me HTML converted from author's Markdown entered in the CMS. So how was I supposed to wire this up to launch in a modal or some overlay to get the desired behavior? The content was rendered with our favorite, yet scary named attribute, dangerouslySetInnerHTML.

So I asked Twitter:

But I didn't get any replies, because who wants to launch modals anyway?

The Solution

I knew I wanted to leverage the modal-routing Gatsby plugin, seeing as it added support for viewing other Gatsby routes within a modal view. I figured I had to somehow replace the standard anchor elements for the <Link> component, but how? All I had was a string of HTML elements to pass into a div:

export const HTMLContent = ({ content, className }) => (
  <div className={className} dangerouslySetInnerHTML={{ __html: content }} />
)

Then I stumbled upon this simple, yet perfect library, htmr, which takes an HTML string and converts it to valid React elements. Pass it your transform rules and magically the original string morphs into your React elements for rendering, with the rules applied! For example, from the library's documentation:

const transform = {
  p: Paragraph,
  // you can also pass string for native DOM node
  a: 'span'
}

// will return <Paragraph><span>{'Custom component'}</span></Paragraph>
convert('<p><a>Custom component</a></p>', { transform });

So I wrote my transform to utilize the <Link> component along with the asModal attribute to get my desired output.

// Transform to leave external links unchanged
// but send internal links to be viewed within a modal
const transform = {
  // When encountering an anchor, <a>, do this
  a: node => {
    const { href } = node;

    // Going outside?
    if (href.substr(0, 4) === "http") {
      return node;
    }

    // Internal link to some other Gatsby route
    return (
      <Link to={href} asModal>
        {node.children}
      </Link>
    );
  }
};

// <a href="/exercise-library/triangle-push-up/">Triangle Push-up</a>
// transforms to
// <Link to="/exercise-library/triangle-push-up/" asModal>Triangle Push-up</Link>
const newHtml = convert(workoutHtml, { transform });

We leave the external links alone, because I'd still want them to navigate to the site it is pointing to. But we transform the internal ones so they are viewed within a modal. Now the user can just click close or off to the side of the screen and be viewing the rest of the workout much more quickly. If I only wanted to do this against a specific route, such as /exercise-library, I could enhance my transform to do so.

Summary

I was extremely happy to come up with such a simple solution to this. Kristie gets to continue writing links through the CMS tool and this conversion all happens behind the scenes. Shout out to Fatih Kalifa for his fantastic conversion library. I'm sure there could be a more Gatsby-like way of solving this issue, so if you have any knowledge on writing Gatsby plugins and think I could have done this differently, I'd love to discuss it with you.

Discuss on Twitter

Share article