MalcMind

How Do You Customize Your ReactMarkdown Components?

react.png

Hello Folks. And welcome to another blog of everything React! This blog will sadly be more exploratory then actually fact checked (for now).

The issue:

I need to customize my ReactMarkdown components to render-in customized code in order to add more functionality to blogposts that I render into my Front-End. Blog Post such the one you are reading now! (I use ReactMarkdown to Render my Strapi blogs)

The Remedy:

I present some of my findings to how you can custom render your React Markdown code snippets.

Method 1: Customize Your ReactMarkdown Components by Dom Manipulation

Lets suppose that you want to manipulate your ReactMarkdown Strings with customized code. I know... a very specific use case... but that's why this blog is experimental!

In a React application using the react-markdown library, if you want to search for a specific string of text within the rendered content of a <ReactMarkdown> component, you can use JavaScript to traverse the DOM and look for the desired text. Here's a basic example of how you might achieve this:

First, make sure you have the react-markdown library installed. You can install it using:

npm install react-markdown

Then, you can use the following example code to search for a string of text within a <ReactMarkdown> component:

import React, { useState, useEffect } from 'react';
import ReactMarkdown from 'react-markdown';

const MarkdownComponent = ({ markdownContent, searchString }) => {
  const [foundText, setFoundText] = useState(false);

  useEffect(() => {
    const searchInMarkdown = () => {
      const markdownElement = document.getElementById('markdown-content');

      if (markdownElement) {
        const textInMarkdown = markdownElement.innerText || markdownElement.textContent;

        if (textInMarkdown.includes(searchString)) {
          setFoundText(true);
        } else {
          setFoundText(false);
        }
      }
    };

    searchInMarkdown();
  }, [markdownContent, searchString]);

  return (
    <div>
      <ReactMarkdown id="markdown-content" source={markdownContent} />
      {foundText && <p>Text found!</p>}
    </div>
  );
};

export default MarkdownComponent;

In this example:

  1. The ReactMarkdown component renders the provided markdownContent inside a div with the id "markdown-content".
  2. The useEffect hook runs whenever the markdownContent or searchString changes.
  3. Inside the useEffect, the function searchInMarkdown gets the text content of the markdownElement and checks if it includes the specified searchString.
  4. If the searchString is found, it sets the foundText state to true, and you can then render a message or take any other action.

Remember to replace the markdownContent and searchString props with your actual markdown content and the string you want to search for. Additionally, you may need to adjust this code based on your specific requirements and the structure of your React application.

Method 2: HTML ReactMarkdown Manipulation

You have the option to write your Markdown pages with HTML code. Keep in mind - The react-markdown library in React is designed to render Markdown content, not raw HTML. By default, it does not render HTML tags directly for security reasons, as rendering raw HTML can expose your application to potential cross-site scripting (XSS) vulnerabilities.

However, if you trust the source of your Markdown content and want to render HTML tags within the Markdown, you can use the escapeHtml prop in react-markdown and set it to false. This tells the library not to escape HTML tags and render them as-is.

Here's an example:

import React from 'react';
import ReactMarkdown from 'react-markdown';

const MarkdownComponent = ({ markdownContent }) => {
  return (
    <div>
      <ReactMarkdown source={markdownContent} escapeHtml={false} />
    </div>
  );
};

export default MarkdownComponent;

Keep in mind that enabling escapeHtml={false} should be done with caution, especially if the Markdown content comes from user input, as it might expose your application to security risks. Ensure that you trust the source of the content or sanitize it appropriately to avoid potential security issues.

If you need to render raw HTML safely, you might want to consider using a dedicated HTML rendering library or a sanitizer library in addition to or instead of react-markdown.

Method 3: Markdown Attribute Manipulation

This was an Idea I got reading MarkdownMonster

They suggest adding Generic attributes as a Markdown extension to support code such as this:

# CSS Classes{.WARNING}
Using `.css-class` syntax lets you apply custom CSS classes.

## Id Values{#Updates}
Using `#id` lets you apply custom ID values.

## Custom Styling{style="color:darkgoldenrod"}
For everything else you can use attributes

To my knowledge the react-markdown library doesn't have native support for parsing and applying custom attributes. By default, it focuses on standard Markdown syntax.

However, if you need to handle custom attributes in your Markdown content, you might want to consider using a custom renderer. You can create a custom renderer that extends the default renderer and allows you to handle additional properties or attributes.

Here's a simplified example of how you might approach this:

import React from 'react';
import ReactMarkdown from 'react-markdown';

const MyCustomMarkdownRenderer = ({ node, ...props }) => {
  if (node.type === 'heading') {
    // Check for CSS class attribute
    const className = node.data && node.data.hProperties && node.data.hProperties.className;
    const id = node.data && node.data.hProperties && node.data.hProperties.id;
    const style = node.data && node.data.hProperties && node.data.hProperties.style;

    if (className || id || style) {
      return React.createElement(
        `h${node.depth}`,
        {
          className: className || '',
          id: id || '',
          style: style || {},
        },
        props.children
      );
    }
  }

  // For other elements, you can extend this logic

  // Fallback to the default renderer
  return ReactMarkdown.renderers.heading(node, props);
};

const MarkdownComponent = ({ markdownContent }) => {
  return (
    <div>
      <ReactMarkdown
        source={markdownContent}
        renderers={{ heading: MyCustomMarkdownRenderer }}
      />
    </div>
  );
};

export default MarkdownComponent;


This example is focused on handling custom attributes for headings, but you can extend this logic to other Markdown elements as needed. Keep in mind that this is a basic example, and you may need to adjust it based on your specific requirements.

Please note that using custom attributes in Markdown might make your content less portable across different Markdown parsers or platforms. If portability is a concern, you may want to consider using standard Markdown syntax and applying styling or attributes programmatically in your React application based on the parsed Markdown content.

Bonus Section:

I was reading a StackOverflowReactMarkdown blog that had a code example (non functioning) of a user importing a plugin attempting to correct his particular issue:

const ReadMePreviewer = ({ readMeCode }) => {
  return (
    <ReactMarkdown
      plugins={[[gfm, { singleTilde: false }]]}
      className={style.reactMarkDown}
      // className={style.reactMarkDownRemovingExtraGlobal}
      renderers={renderers}
      source={readMeCode}
    />
  );
};

I had no idea that you can import plugins into ReactMarkdown, or what the end answer was for this use case, but that blogpost is worth further investigation. https://www.bayanbennett.com/posts/markdown-with-custom-components-in-nextjs-devlog-007/ is another blog worth investigating.

Conclusion

I will probably try HTML edits first, as that seems to be the most portable codewise speaking - although drawbackwise are that it could lead to XSS exploits if you happen process any of the Information on the Backend. I will be sure to take messures to restrict those components to the Front-End (Next.js makes SSR rendering and client side rendering easy with the 'use server' and 'use client' tags.

While the other Methods seem to have merit as well, I will be sure to update this blogpost once I run through all the methods!