Reputation: 8340
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
Reputation: 3032
Based on the similar solution to render curly braces, I guess we can use HTML characters to substitute them:
<!-- comment -->
Upvotes: 0
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
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
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
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
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.
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
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:
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:
Upvotes: 5
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
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
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
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