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"}
>
ReactDOM.renderToStaticMarkup()?