How to render a HTML comment in React?

33,281

Solution 1

This is what I have ended up with in one of my recent projects:

import React, {Component, PropTypes} from 'react';
import ReactDOM from 'react-dom';

class ReactComment extends Component {
    static propTypes = {
        text: PropTypes.string,
        trim: PropTypes.bool
    };

    static defaultProps = {
        trim: true
    };

    componentDidMount() {
        let el = ReactDOM.findDOMNode(this);
        ReactDOM.unmountComponentAtNode(el);
        el.outerHTML = this.createComment();
    }

    createComment() {
        let text = this.props.text;

        if (this.props.trim) {
            text = text.trim();
        }

        return `<!-- ${text} -->`;
    }

    render() {
        return <div />;
    }
}

export default ReactComment;

So you can use it like this:

<A>
    <B></B>
    <ReactComment text="<fragment>" />
        <C></C>
        <D></D>
     <ReactComment text="</fragment>" />
    <E></E>
</A>

Solution 2

Here's another novel approach if you need this to work with SSR.

Here's a MaxWidth component I am using with my react-based email tool called Myza.

import ReactDOMServer from 'react-dom/server'

export const MaxWidth = ({ maxWidth = 0, className, children }: IMaxWidthProps) => {
  const renderedChildren = ReactDOMServer.renderToStaticMarkup(
    <div className={className} style={{ maxWidth: `${maxWidth}px`, margin: '0 auto' }}>
      {children}
    </div>
  )

  return <div dangerouslySetInnerHTML={{
    __html: `
    <!--[if mso]><center><table><tr><td width="${maxWidth}"><![endif]-->
    ${renderedChildren}
    <!--[if mso]> </td></tr></table></center><![endif]-->
  ` }}
  />
}

Solution 3

You can do it with the following component, it is simple and functional but it has the drawback of having to wrap your comment in a HTML node i.e. a "div" because it makes use of the dangerouslySetInnerHTML attribute:

    const ReactComment = ({ text }) => {
  return <div dangerouslySetInnerHTML={{ __html: `<!-- ${text} -->` }}/>
}

Then, you use it like so:

<ReactComment text={'My beautiful HTML comment'}/>

Solution 4

HTML Comments in React

To render comments in React (which is what I'm guessing most people are looking for when they come to this question), I use a react component which I have in a gist. It was based off of the answer by Alex Zinkevych, but with the following improvements:

  • Updates to props now trigger the component to update, so the comment can be more dynamic
  • The component cleans up after itself
  • The div is hidden before being swapped out for the comment node
  • (Code Style) React Ref used instead of ReactDOM.findDOMNode(this), which is the recommended way of interacting with the DOM elements, according to React's documentation.

I linked to the gist above, but I've also copied the content at the time of this writing below, but you might want to see if there's any revisions on the gist, since I will fix any bugs I might find and post as revisions to the Gist.

import * as React from 'react';
import * as ReactDOM from 'react-dom';

interface IProps {
    text: string;
}

export class HTMLComment extends React.Component<IProps> {
    private node: Comment;
    private ref$rootDiv = React.createRef<HTMLDivElement>();

    constructor(props: IProps) {
        super(props);

        this.node = window.document.createComment(props.text);
    }

    componentDidMount() {
        if (this.ref$rootDiv && this.ref$rootDiv.current) {
            let divElement = this.ref$rootDiv.current;

            // Tell React not to update/control this node
            ReactDOM.unmountComponentAtNode(divElement);

            // Replace the div with our comment node
            this.ref$rootDiv.current.replaceWith(this.node);
        }
    }

    componentDidUpdate(prevProps: IProps) {
        if (prevProps.text !== this.props.text) {
            this.node.textContent = this.props.text;
        }
    }

    componentWillUnmount() {
        this.node.remove();
    }

    render() {
        return (
            <div
                ref={this.ref$rootDiv}
                style={{
                    display: 'none',
                }}
            />
        );
    }
}

Answering the Actual Question

However, as the OP noted in a comment on Alex's post, this doesn't actually answer the question. For a single component that renders comments before and after the children, we can use the HTMLComment component defined above and compose a new component:

interface IHTMLCommentWrapperProps {

}

const HTMLCommentWrapper: React.FunctionComponent<IHTMLCommentWrapperProps> = (props) => {
    return (
        <React.Fragment>
            <HTMLComment text={`<fragment data-reactid="">`} />
            {props.children}
            <HTMLComment text={`</fragment>`} />
        </React.Fragment>
    )
}

Now, we can put all this together into one script. Here is that source code over at the Typescript playground, as well as a Gist (it's large and repeast the components detailed above, so I won't copy that code directly into this answer.

We can copy the compiled javascript into the snippet below:

class HTMLComment extends React.Component {
    constructor(props) {
        super(props);
        this.ref$rootDiv = React.createRef();
        this.node = window.document.createComment(props.text);
    }
    componentDidMount() {
        if (this.ref$rootDiv && this.ref$rootDiv.current) {
            let divElement = this.ref$rootDiv.current;
            // Tell React not to update/control this node
            ReactDOM.unmountComponentAtNode(divElement);
            // Replace the div with our comment node
            this.ref$rootDiv.current.replaceWith(this.node);
        }
    }
    componentDidUpdate(prevProps) {
        if (prevProps.text !== this.props.text) {
            this.node.textContent = this.props.text;
        }
    }
    componentWillUnmount() {
        this.node.remove();
    }
    render() {
        return (React.createElement("div", { ref: this.ref$rootDiv, style: {
                display: 'none',
            } }));
    }
}
const HTMLCommentWrapper = (props) => {
    return (React.createElement(React.Fragment, null,
        React.createElement(HTMLComment, { text: `<fragment data-reactid="">` }),
        props.children,
        React.createElement(HTMLComment, { text: `</fragment>` })));
};
const A = (props) => { return React.createElement("a", null, props.children); };
const B = (props) => { return React.createElement("b", null, props.children); };
const C = (props) => { return React.createElement("c", null, props.children); };
const D = (props) => { return React.createElement("d", null, props.children); };
const E = (props) => { return React.createElement("e", null, props.children); };
const App = () => {
    return (React.createElement(A, null,
        React.createElement(B, null),
        React.createElement(HTMLCommentWrapper, null,
            React.createElement(C, null),
            React.createElement(D, null)),
        React.createElement(E, null)));
};
let el$root = document.getElementById('react-app');
if (el$root) {
    ReactDOM.render(React.createElement(App, null), el$root);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

<div id="react-app"/>

If you run this snippet and inspect the HTML, you'll see the following:

enter image description here

Share:
33,281
Greg
Author by

Greg

Updated on July 09, 2022

Comments

  • Greg
    Greg almost 2 years

    Currently the render method can only return a single element/component. See: here

    In the discussion under that ticket some suggest to wrap multiple elements returned from a React component in a HTML comment so that the wrapping component is ignored by the browser, e.g.:

    <A>
        <B></B>
        <Fragment>
            <C></C>
            <D></D>
        </Fragment>
        <E></E>
    </A>
    

    would render to:

    <a>
        <b></b>
        <!--<fragment data-reactid="">-->
            <c></c>
            <d></d>
        <!--</fragment>-->
        <e></e>
    </a>
    

    But how to actually create a component that renders just HTML comment? In other words, how the render function of the 'fragment' component in the example above could look like?

  • Greg
    Greg over 7 years
    Thanks, but as far as I understand this code it doesn't answer my question. My aim isn't to render a comment in React but to return from a render function a single element, which renders two comments, one above and one below its children. In other words, I should be able to use it like this: <Fragment><C /><D /></Fragment> and it should render the children with two comments, one above and one below, as in the example in my question.
  • Andy Lorenz
    Andy Lorenz about 6 years
    6 upvotes for an answer that doesn't even provide a solution for the problem posed?! This is just how to put comments in your component code, but (like me) the OP wants to output comments into his rendered html !
  • Jake Sylvestre
    Jake Sylvestre about 5 years
    This will not render the comments into html comments <!-- comment -->. They won't even come out in minified source since a transpiler will take them out
  • FibreFoX
    FibreFoX over 4 years
    This solution seems to not work when using ReactDOMServer.renderToStaticMarkup
  • omnibrain
    omnibrain over 4 years
    It will also crash when react is trying to unmount the component because it can't find the child node that it is expecting in the DOM.
  • r.k0
    r.k0 over 3 years
    doesnt't crash during unmounting for me, but interested in any other drawbacks
  • zhuhang.jasper
    zhuhang.jasper almost 3 years
    cool. creating custom component for comment.
  • zomars
    zomars almost 2 years
    I assume this won't work for SSR since we're using componentDidMount and window.
  • zomars
    zomars almost 2 years
    I assume this won't work for conditional tags inside head elements since div cannot be used inside head.
  • Jonathon Richardson
    Jonathon Richardson almost 2 years
    Depends on exactly what you're trying to accomplish. If you want the server side rendered pages to have the comments inline, no.
  • Jonathon Richardson
    Jonathon Richardson almost 2 years
    However, if you're ReactDOM.hydrate()ing the page, you'll be able to see the comments in the inspector once the lifecycle methods have fired. You could get comments in the initial SSR by using hidden elements (e.g. <div hidden />), possibly using a custom element (<ns-comment hidden />), and you could emit that for SSR, and use the lifecycle methods to replace the custom element with real HTML comments at hydration/run time, if you want. Kind of depends on why you want the comments.
  • Jonathon Richardson
    Jonathon Richardson almost 2 years
  • Qwerty
    Qwerty almost 2 years
    Did you use Prettier for the ReactComment component?