Unable to modify children in parsed html

3.6k Views Asked by At

I want to render a large chunk of html by replacing specific tags such as <p> and <a> with React components(i.e. <StyledParagraph> and <StyledLink>)

I've tested many libraries including html-react-parser. Unlike many others, html-react-parser has an example that addresses my need. But, I cannot get the example to work. (Using React 16.5.3)

const test = () =>
  <>
    {parse(
      `
      <p id="main">
        <span class="prettify">
          keep me and make me pretty!
        </span>
      </p>
    `,
      {
        replace: ({ attribs, children }) => {
          if (!attribs) return;

          if (attribs.id === 'main') {
            return (
              <h1 style={{ fontSize: 42 }}>
                {domToReact(children)}
              </h1>
            );
          } else if (attribs.class === 'prettify') {
            return (
              <span style={{ color: 'hotpink' }}>
                {domToReact(children)}
              </span>
            );
          }
        }
      }
    )}
  </>

I did not pass parserOptions to the domToReact function, because I haven't found the documentation for those options yet.

Expected results:

<h1 style="font-size:42px">
  <span style="color:hotpink">keep me and make me pretty!</span>
</h1>

So far, I cannot modify the nested nodes (in this case <span>).

Actual results:

<h1 style="font-size: 42px;">
    <span class="prettify">keep me and make me pretty!</span>
  </h1>
2

There are 2 best solutions below

0
On

I have this working without domToReact option. I am passing an HTML string from a JSON object fetched from Wordpress to react-syntax-highlighter. I haven't examined the html-react-parser source code yet to see what the difference is between using the two, but replacing an HTML tag with JSX appears to work with domNode.attribs just fine.

This component takes that JSON object (which is comprised of Wordpress posts), maps each post to a separate parsed section, and then replaces each pre tag of each parsed section that meets a certain criteria (in this case, class=wp-block-code or class=CodeMirror) with a JSX SyntaxHighlighter tag, while retaining the text of the codeblock (domNode.children[0].data).

Notes: While analyzing the JSON object, I saw that none of these domNode.children arrays had more than one position, so position [0] was all that was required in my case - obviously, your needs may vary.

None of the commands from html-react-parser or JSON objects retrieved from Wordpress are destructured, so you can see the clear delineation of the API/commands.

import parse from 'html-react-parser';
import SyntaxHighlighter from 'react-syntax-highlighter';
import { gruvboxDark } from 'react-syntax-highlighter/dist/esm/styles/hljs';

export const Blog = (props) => {
  return (
    <div>
      <h1>This is an example of a blog component</h1>
      <div>
        {props.posts.map((post, index) => (
          <div key={post.id}>
            <h3>{post.id}</h3>
            <div>{parse(post.title.rendered)}</div>
            <div>{parse(post.excerpt.rendered)}</div>
            <div>
              {parse(post.content.rendered, {
                replace: (domNode) => {
                  if (
                    (domNode.attribs && domNode.attribs.class === 'CodeMirror') ||
                    (domNode.attribs && domNode.attribs.class === 'wp-block-code')
                  ) {
                    return (
                      <SyntaxHighlighter
                        language="bash"
                        showLineNumbers={true}
                        style={gruvboxDark}
                        wrapLongLines={true}
                      >
                        {domNode.children[0].data}
                      </SyntaxHighlighter>
                    );
                  }
                },
              })}
            </div>
          </div>
        ))}
      </div>
    </div>
  );
};
0
On

You have to pass a replace function to domToReact as part of its options param. From the source:

/**
 * Converts DOM nodes to React elements.
 *
 * @param  {Array}    nodes             - The DOM nodes.
 * @param  {Object}   [options]         - The additional options.
 * @param  {Function} [options.replace] - The replace method.
 * @return {ReactElement|Array}
 */

In the case of this example that would mean...

function replaceHtmlWithReact({ attribs, children }) {
  if (!attribs) return;

  if (attribs.id === 'main') {
    return (
      <h1 style={{ fontSize: 42 }}>
        {domToReact(children, { replace: replaceHtmlWithReact })}
      </h1>
    );
  } else if (attribs.class === 'prettify') {
    return (
      <span style={{ color: 'hotpink' }}>
        {domToReact(children, { replace: replaceHtmlWithReact })}
      </span>
    );
  }
}

const test = () => 
<>
  {parse(
    `
      <p id="main">
        <span class="prettify">
          keep me and make me pretty!
        </span>
      </p>
    `,{ replace: replaceHtmlWithReact }
  )}
</>