How to render a HTML comment in React?
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:
Greg
Updated on July 09, 2022Comments
-
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 over 7 yearsThanks, 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 about 6 years6 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 about 5 yearsThis 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 over 4 yearsThis solution seems to not work when using
ReactDOMServer.renderToStaticMarkup
-
omnibrain over 4 yearsIt 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 over 3 yearsdoesnt't crash during unmounting for me, but interested in any other drawbacks
-
zhuhang.jasper almost 3 yearscool. creating custom component for comment.
-
zomars almost 2 yearsI assume this won't work for SSR since we're using
componentDidMount
andwindow
. -
zomars almost 2 yearsI assume this won't work for conditional tags inside head elements since
div
cannot be used insidehead
. -
Jonathon Richardson almost 2 yearsDepends on exactly what you're trying to accomplish. If you want the server side rendered pages to have the comments inline, no.
-
Jonathon Richardson almost 2 yearsHowever, 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 almost 2 years
-
Qwerty almost 2 yearsDid you use Prettier for the ReactComment component?