rfcs icon indicating copy to clipboard operation
rfcs copied to clipboard

RFC: RawHTML Component

Open drKnoxy opened this issue 5 years ago • 27 comments

drKnoxy avatar Nov 06 '19 14:11 drKnoxy

One day there was a proposal to use Fragment for this, ie <Fragment dangerouslySetInnerHTML...> as it does not include "any wrapping element"

theKashey avatar Nov 06 '19 21:11 theKashey

@theKashey https://github.com/facebook/react/issues/12014

dangayle avatar Nov 25 '19 17:11 dangayle

I only need to point out while it is not relevant directly I have never seen an HTML Processing Framework that does not allow to easily use RAW HTML respect that point is earned by React. I will keep that example referenced for years it will illustrate how scary you can design a frontend lib.

At present people need to parse HTML into a DOM Tree to parse it back into react create an element and react itself offers nothing to assist in that process.

compare that to for example vue it uses raw html :) nothing needed to use rawHtml :)

frank-dspeed avatar Feb 26 '20 06:02 frank-dspeed

I really dont understand why this is not possible right now...

b0z1 avatar Apr 17 '20 13:04 b0z1

I think, since React gets used more and more with things like gatsby and thus with CMS's, a RawHTML-Component would make sense.

smetzdev avatar Apr 30 '20 06:04 smetzdev

Is there a workaround that is available now?

EDIT: Look like the most solid workaround is Interweave - it supports SSR as well!

tyteen4a03 avatar Jun 19 '20 23:06 tyteen4a03

@tyteen4a03 yes that is one possible solution the general solution is to use a parser like "parse5": "^5.1.1", thats what your package is using and write a tree adapter for react i am working on one at present

frank-dspeed avatar Jun 21 '20 05:06 frank-dspeed

I feel like this would be a very helpful feature for the project I'm currently working on. 👍

Doing universal rendering with micro frontends created a demand for being able to inject raw HTML inside React components for us and I don't feel like serving the entire HTML parser to the client just to get rid of the redundant wrapper.

Is there a consensus on RawHtml vs Fragments?

I hope this gets RFC some attention!

halfzebra avatar Jun 25 '20 09:06 halfzebra

This feature will make injecting custom html into _document in next.js much easier. vercel/next.js#3105 vercel/next.js#3904

ThewBear avatar Aug 28 '20 17:08 ThewBear

Hey @bvaughn 👋 you mentioned the best way forward is the RFC process. Can you help direct us what the next step is from here?

I saw the README mentioned "Eventually, the team will decide whether the RFC is a candidate for inclusion" but it's unclear if we need to ping you all, or maybe submit this RFC for team review perhaps?

jayphelps avatar Sep 01 '20 15:09 jayphelps

@jayphelps RFC process is still the appropriate path for this kind of issue/feature, but we can't guarantee any kind of timeline for RFC review or approval. (For what it's worth, the team does generally try to read and keep up-to-date with open RFCs. The approval process can be very time consuming on our side though and we just don't have the bandwidth for much extra at the moment.)

bvaughn avatar Sep 01 '20 16:09 bvaughn

My use case for this is in a gatsby project using server side rendering (gatsby-ssr.js) to populate the contents of head with the inline script for a NewRelic Browser Agent fetched using this api which produces a json document with the NewRelic loader script e.g.:

{
  "browser_applications": [
    {
      "id": 123456789,
      "name": "my-app-name",
      "browser_monitoring_key": "NRJS-9999999999999999999",
      "loader_script": "<script type=\"text/javascript\">\n;window.NREUM||(NREUM={});NREUM.init ... snip ...

So wrapping the contents of loader_script in a div would of-course be incorrect. There are other ways I can solve this, however the proposed RawHTML would be the cleanest!

tonymcneil avatar Feb 12 '21 07:02 tonymcneil

In the spirit of #182 — no particular update on this. We're doing work on a new streaming server renderer (https://github.com/reactwg/react-18/discussions/37) so I'll check with the team if there are any new constraints that would influence this.

gaearon avatar Aug 18 '21 21:08 gaearon

A use case of this is to render something like this:

['<p>', someReactElement, '</p>']

with react fragments accepting dangerouslySetInnerHTML it would be written as:

[
  <React.Fragment dangerouslySetInnerHTML={{__html: '<p>'}} />,
  someReactElement,
  <React.Fragment dangerouslySetInnerHTML={{__html: '</p>'}} />
]

As far as I understand if this array is generated dynamically, this is something not possible to achieve with React and there is no workaround.

Edit: Does anyone know if it is possible at all to support this with current features of the DOM?

sassanh avatar Oct 24 '21 19:10 sassanh

@sassanh it is possible the current state is you take a HTML/Parser and then you write a small script that translates the resulting PARSER-DOM to React-DOM in this issue are some links to existing solutions you should read it complet

https://github.com/milesj/interweave/

frank-dspeed avatar Oct 25 '21 05:10 frank-dspeed

@frank-dspeed Thanks! But I took a quick look but I really doubt if someReactElement between <p> and </p> will be rendered in a way that it preserves its event listeners or any other logic attached to it, I think interweave will just render the HTML (which I'm already able to do) but what I'm looking for is to preserve the react elements logic (they may have onClick, etc attached to their elements, timers set with setTimeout, fetching data from servers and render stuff based on that, etc) Please let me know if interweave can handle it as well.

sassanh avatar Oct 25 '21 05:10 sassanh

@sassanh as interwave is a react-element you can put your other react element next to it

<inter.... /><your-component ..... /><inter ...... />

[
  <inter ..../>,
  someReactElement,
  <inter ... />
]

with the matchers you could code a custom matcher that injects your react element into a single <inter ... />

frank-dspeed avatar Oct 25 '21 06:10 frank-dspeed

I think interweave is not solving the problem I mentioned, if I wanted to parse the text containing the HTML elements and get aware of its structure I could resolve the issue without interweave too, but if React supported something like this:

[
  <ReactDOM.RawHTML dangerouslySetInnerHTML={{__html: '<p>'}} />,
  someReactElement,
  <ReactDOM.RawHTML dangerouslySetInnerHTML={{__html: '</p>'}} />
]

which I think is extremely hard to support, then I didn't need to be aware of the structure of the string passing to dangerouslySetInnerHTML and it would work with things like this out of the box:

[
  <ReactDOM.RawHTML dangerouslySetInnerHTML={{__html: veryComplexHTMLPart1}} />,
  aReactElementBasedOn_veryComplexHTMLPart2_,
  <ReactDOM.RawHTML dangerouslySetInnerHTML={{__html: veryComplexHTMLPart3}} />
]

sassanh avatar Oct 25 '21 15:10 sassanh

A usecase that would be a bit easier to achieve:

    <>
      {/* It is not possible to render the html of a React-rendered component without a container
          because dangerouslySetInnerHTML is the only route to get raw html into the resulting html */}
      <kaliber-component-container dangerouslySetInnerHTML={{ __html: renderedComponent }} />

      {/* Use render blocking script to remove the container and supply the correct  comment nodes.
          This ensures the page is never rendered with the intermediate structure */}
      <script dangerouslySetInnerHTML={{ __html: restructureDomNodes(componentInfo) }} />
    </>
function restructureDomNodes(componentInfo) {
  return `|var d=document,s=d.currentScript,p=s.parentNode,c=s.previousSibling;
          |p.setAttribute('${containerMarker}','');                             // set marker on container so we can retrieve nodes that contain components
          |p.replaceChild(d.createComment('start'),c);                          // replace kaliber-component-container element with a 'start' comment
          |p.insertBefore(d.createComment(JSON.stringify(${componentInfo})),s); // create a comment containing the component info
          |Array.from(c.childNodes).forEach(x=>{p.insertBefore(x,s)});          // insert all children from the kaliber-component-container element
          |p.replaceChild(d.createComment('end'),s);                            // create an 'end' comment
          |`.replace(/^\s*\|/gm, '').replace(/\s*\/\/.*$/gm, '').replace(/\n/g, '')
}

We use this to render components without a container on the server. On the client we extract the nodes to hydrate them.

This strategy allows us to render layout containers on the server and have CSS layout their interactive children without the hassle of an 'unneeded' container around the interactive child.

EECOLOR avatar Nov 14 '21 22:11 EECOLOR

@sassanh <React.Fragment dangerouslySetInnerHTML={{__html: '<p>'}} />

React is a cross-platform framework (React Native, Ink, other custom renderers etc.), so DOM-specific props on React.Fragment is unlikely.

satya164 avatar Nov 15 '21 09:11 satya164

@satya164 Right, I will replace React.Fragment with ReactDOM.RawHTML then.

sassanh avatar Nov 15 '21 09:11 sassanh

This probably isn't a huge interest, but I'm working on writing a tool to translate google closure templates (aka soy templates) into React/JSX and this would be really helpful in maintaining the output behavior

benmcginnis avatar Mar 02 '22 23:03 benmcginnis

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

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

Usage:


const EmailHead = ({ title = "" }) => {
  return (
    <head>
      <title>{title}</title>
      <RawHtml 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>
image

EDIT: Simplified usage a little bit, also if you're planning to use renderToStaticMarkup, you can do this to cleanup the empty scripts:

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

zomars avatar May 29 '22 05:05 zomars

I found many opensource participants, but no one from React team with power to check if this RPC is acceptable or need to be upgrade?

I know there is no guarantee for RFC timeline, but it has been 2 years.

denghongcai avatar Aug 02 '22 02:08 denghongcai

@denghongcai the problem is that it does not rely work with the core principles of how react works

react parses dom and or html strings into unfed react dom model so the way to create compat is to parse html to the react dom model to enable raw HTML

while out of dev view it is more simple to isolate react components into its own parts and not attach a parsed tree to the react part

maybe i should draw a model to make that more understand able.

<raw html />
<some_react />

never!

<raw html />
<some_react><raw html /></some_react>

i know this maybe gets downvotes because it is handy to take react apps that use the whole document and then modifie them but thats how react is that is not my opinion but the code that is needed to take raw html to react dom is a beast it self.

all existing solutions do parse the HTML to ReactDOM

frank-dspeed avatar Aug 02 '22 05:08 frank-dspeed

Obviously this is incredibly stalled, but is there any possibility of dangerouslySetOuterHTML on regular DOM elements in JSX? There's a good chance it would screw with the VDOM, but man I would just like to see any solution to get this issue chugging along.

EthanStandel avatar Feb 01 '24 16:02 EthanStandel

I would love to have this feature. I currently have a need to insert regular HTML comment in the React rendered application, that will be not removed during the rendering, and there is not easy way to do it. I can do workaround with document.createComment() and append it inside the node I need, but it removed and ease with which React allows us to define order of the children in the dom tree.

Kiwka avatar Sep 20 '24 12:09 Kiwka