Search code examples
javascriptreactjsmarkdowndangerouslysetinnerhtmlreact-markdown

React markdown - how to get nth-element and append another element using react-markdown or dangerouslySetInnerHTML?


I'm looking to append a button after the 2nd paragraph in a react html markdown implementation. I am currently using react-markdown to compile my html code. A few things I am trying to do here:

  1. Get nth element of the react markdown html (ex: p:2nd-child)
  2. Create new element (ex: document.createElement('button'))
  3. Append after the (p:2nd-child) element the new button

Should I use ref to achieve this or is it as simple as appending with plain JavaScript?? Open to other suggestions if there is a better solution.

index.jsx

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

const Markdown = (props) => {
  // props.contentHTML seems to be typeof string (with no html tags. 
  //ex: 'hello world' vs '<p>hello world</p>') but converts to appropriate element tags
  const reactHTML = <ReactMarkdown children={props.contentHTML} />

  useEffect(() => {
    // how do i get 2nd element of that reactHTML p tag??
    let secondPElement = ?? reactHTML.querySelector..... ??

    // create element for my button
    let button = document.createElement('button');
    button.id = 'btn-1';
  
    // error here: appendChild does not exist on type 'Element'
    reactHTML.appendChild(button)
  })

  return (
    {reactHTML}
  )

} 

export default Markdown

Solution

  • Unfortunately, I don't think there's a way to achieve this without doing something a little unorthodox (read: hacky).

    TLDR: Here's a sandbox with what I think is a viable solution for you.

    Continuing on, there are a couple of problems preventing your code from working.

    When you define reactHTML you're not actually defining HTML but rather a react Element Object (because that's what JSX compiles it into).

    This means that you won't be able to use DOM selectors on it (e.g. querySelector and appendChild) and that is why you get the error appendChild does not exist on type 'Element'. This makes sense because the react Element object does not have such a method.

    The "solution" then, is to render your markdown content as you normally would, and only after being rendered go in and access the elements that you want. This can be easily achieved with the useEffect hook very similar to what you're already doing:

    const Markdown = (props) => {
      const markdown = `
      This is rendered as a '<p>' element
    
      So is this one!
    
      Me too!
      `;
    
      useEffect(() => {
        // Get all of the <p> elements
        const pElements = document.getElementsByTagName("p");
    
        // Create a button
        const button = document.createElement("button");
        button.id = "btn-1";
        button.innerHTML = "I'm a button!";
    
        const refChild = pElements[1];
        // Insert the button before the next sibling of the second <p> tag
        refChild && refChild.parentNode.insertBefore(button, refChild.nextSibling);
      }, []);
    
      return <ReactMarkdown children={markdown} />;
    };