Greg
Greg

Reputation: 8340

How to render a HTML comment in React?

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?

Upvotes: 56

Views: 49807

Answers (11)

Ooker
Ooker

Reputation: 3032

Based on the similar solution to render curly braces, I guess we can use HTML characters to substitute them:

&lt!-- comment --&gt

Upvotes: 0

Sambeau
Sambeau

Reputation: 254

This works, and gives you proper comment tag, rather than a div…

useEffect(() => {
    const c = document.createComment(`Hi there!`);
    document.appendChild(c);
    return () => document.removeChild(c);
}, []); 

Upvotes: 0

ioannis.th
ioannis.th

Reputation: 453

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 <b>HTML</b> comment'}/>

Upvotes: 20

zomars
zomars

Reputation: 347

I feel the need to post my answer here since this is where I first landed when searching for this.

I know this is a hack but for my use case this allows me to inject arbitrary html inside head tags:

const DangerousRawHtml = ({ html = "" }) => (
  <script dangerouslySetInnerHTML={{ __html: `</script>${html}<script>` }} />
);

Usage:


const EmailHead = ({ title = "" }) => {
  return (
    <head>
      <title>{title}</title>
      <DangerousRawHtml html={`<!--[if !mso]><!--><meta http-equiv="X-UA-Compatible" content="IE=edge"><!--<![endif]-->`} />
    </head>
  )
}

The output will leave some empty script tags along the way which is not optimal but it works:

<html>
  <head>
    <title>Title</title>
    <script></script>
    <!--[if !mso]><!-->
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <!--<![endif]-->
    <script></script>
  </head>
  <body></body>
</html>

Also if you're planning to use renderToStaticMarkup, you can do this to cleanup the empty scripts:

ReactDOMServer.renderToStaticMarkup(<MyRootComponent />)
  // Remove `<DangerousRawHtml />` injected scripts
  .replace(/<script><\/script>/g, "")

Upvotes: 4

user12246115
user12246115

Reputation:

I have seen some answers here say to use a syntax similar to {'<!-- comment -->'} which would simply display <!-- comment --> as a <p> on your browser, it may work if you do the same thing with a ref and then set the ref.current.outerHTML = '<!-- comment -->', but that is extremely tedious and requires useEffect, useRef and a lot of extra code. And you still need to create a throw away div that is replaced with the comment so unless you're going out of your way to try to trick users into thinking you added a comment (which if they know how to inspect a page and view comments then they most likely also know how to read the React JS that your sending too)

A very simple and compact solution that I have used when I want to add a comment is this:

<div style={{display:'none'}}>
    comment
</div>

Upvotes: 6

kronus
kronus

Reputation: 912

Create a functional component with a file name Comment.js

Import jquery to select divs using their class selectors, in combination with using native javascript document.createComment

Use props to pass the text to be used in the comments, as well as, the names of the divs to select:

import $ from 'jquery';

const Comment = (props) => {
  const commentMakerBegin = () => {
    setTimeout(() => {
      const beginComment = document.createComment(props.beginComment);
      const firstElement = $('.' + props.beforeDiv);
      firstElement.before(beginComment);
    }, 1000);
  };

  const commentMakerEnd = (event) => {
    setTimeout(() => {
      const endComment = document.createComment(props.endComment);
      const secondElement = $('.' + props.afterDiv);
      secondElement.after(endComment);
    }, 1000);
  };
  return (
    <>
      {commentMakerBegin()}
      {props.children}
      {commentMakerEnd()}
    </>
  );
};

export default Comment;

The props.children renders whatever is between your custom component tags:

{props.children}

Whether you type a string like 'Your components here' or '<C /><D />' it will render what you type between the opening and closing tags.

In the component that you would like to use the newly created Comment component, import it, followed by passing the text through props that you would like to use for the opening and closing comments

The following image is how I render comments before and after my two modals - consumer-modal and policy-modal respectively.

You can see in the console that the comments have been added

In my App.js file, I import the Comments component and use it in the following manner, which results in the aforementioned screen shot:

     <Comment
        beforeDiv='consumer-modal'
        afterDiv='policy-modal'
        beginComment='modal begins'
        endComment='modal ends'
      >
        <ConsumerModal
          title='testing'
          content={<ConsumerModalContent />}
          onClose={cnsmrModalHandler}
        ></ConsumerModal>
        <PolicyModal
          title='my policy'
          content={<PolicyModalContent />}
          onClose={policyModalHandler}
        />
      </Comment>

Upvotes: -1

Jonathon Richardson
Jonathon Richardson

Reputation: 529

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

Upvotes: 5

briangonzalez
briangonzalez

Reputation: 1616

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]-->
  ` }}
  />
}

Upvotes: 9

Mat Lipe
Mat Lipe

Reputation: 815

Assuming you are on React 16.8+ you may use a small functional component which lets you provide a text property and render an html comment.

import React, {useEffect, useRef} from 'react';

const ReactComment = ( props ) => {
    const el = useRef();
    useEffect( () => {
        el.current.outerHTML = `<!-- ${props.text} -->`;
    }, [] );
    return (
        <div ref={el}/>
    );
};

export default ReactComment;

You may then use it like so

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

Upvotes: 0

Ju-Chien Chien
Ju-Chien Chien

Reputation: 47

Edit: For those who found this answer useful, checkout this answer instead!

The posted problem is not asking for comment style in React!


Use curly brackets, such that you can use javascript comment /* */.

<a>
    <b></b>
    {/*<fragment data-reactid="">*/}
        <c></c>
        <d></d>
    {/*</fragment>*/}
    <e></e>
</a>

Upvotes: -13

Alex Zinkevych
Alex Zinkevych

Reputation: 730

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>

Upvotes: 30

Related Questions