1

I am writing a small system that displays a component and next to it, it displays the source. This is great for developers. However, in some instances I need raw HTML. The only way to get away from this limitation is to duplicate code

Rendering as normal:

<CustomComponent></CustomComponent>

Rendering the encapsulated code "beautified"

<div><div>content</div></div>

What I would like to do is in my application to provide the <CustomComponent> and be able to Render it as normal and also the encapsulated code. I have look at a few solutions but they only provide the solution to render the <CustomComponent></CustomComponent> tags but not the contents inside the render method.

1
  • 1
    ReactDOM.renderToStaticMarkup()? Commented Feb 18, 2020 at 22:52

1 Answer 1

1

How about rendering the component, and then using an effect hook to copy the html as a string into the DOM as text?

/**
 * Component that renders it's children, and then then puts the html source
 * equivalent of that component in a code tag.
 */
function ShowHtml({ children }) {
  // Use refs to store the nodes our after render hook will need.
  const component = useRef(null);
  const code = useRef(null);

  // After render, read the html from the DOM of the component, and insert it
  // as text into the target <code> element.
 useEffect(() => {
    if (code.current && component.current) {
      code.current.innerText = component.current.innerHTML;
    }
  });

  // Render the component, and the code tag.
  return (
    <div>
      <div ref={component}>{children}</div>
      <code ref={code} />
    </div>
  );
}

Usage:

<ShowHtml>
  <Test />
</ShowHtml>

Working example


Caveat: not all props get put in the HTML. Event handlers, for instance, are bound to DOM elements in a different way, with pure javascript. A component is more than just it's HTML, so no matter what, you won't get a complete picture. But you should get any attributes that actually are fully HTML-able with this approach.


Refs are null on first render, because the component has to exist before you can get a reference on the first render. So you have to handle that.

But your new sandbox has another problem. If you need the source at render time in order to pass to a react component, then you cannot use an after-render effect to do that, since rendering is done.

So I am changing my answer to:

import { renderToStaticMarkup } from 'react-dom/server'

Using that function, you should be able to get a string from any rendered react node:

  <div ref={component}>{children}</div>
  <Highlight
    {...defaultProps}
    code={renderToStaticMarkup(children)}
    language={"html"}
  >
Sign up to request clarification or add additional context in comments.

4 Comments

Perfect, even with the caveats I just needed a way to show the component and prevent code duplication which would introduce errors.
One thing @Alex-wayne that I am curios about. I am using prism-react-renderer which provides a <Highlight> component which expects raw HTML to format the code properly. When I feed it the {code} variable to this component it breaks because code is undefined, is there a way to have access to this variable or does it have to be converted to a local const?
I just tested the version you shared by extending it to replicate the issue: codesandbox.io/s/gracious-hooks-tsteq
Thanks again for your help! I ran into another roadblock when moving components into their own components and using Gatsby+MDX. I opened another ticket if you are curios: stackoverflow.com/questions/60309226/…

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.