How can I render a <template /> tag with react?
Asked Answered
M

3

11

Sometimes you might need to render web-components from your react app.

Web-components often use a special <template> ... </template> tag. But if I try to render such markup with react like this:

render() {
  return (
    <template>
      <div>some content</div>
    </template>
  )
}

then my web-components don't work correctly.

Marlow answered 16/3, 2017 at 21:35 Comment(0)
M
13

The reason is that JSX does a different job than what the <template /> tags exists for. The idea of a template tag is to not render its children and pretty much handle it like unparsed text (the browser actually parses it just to make sure its valid html, but does nothing more)

But when you write this in JSX:

return (
  <template>
    <div>some content</div>
  </template>
)

you're basically instructing react to create a 'template' element and then create a 'div' element and then to append this div to the template as a child.

So under hood this happens:

const template = document.createElement('template')
const div = document.createElement('div')
const text = document.createTextNode('some text')
div.appendChild(text)
template.appendChild(div)

But what you want is to set the contents of the <template /> as a string. You can use innerHTML for that.


Solution

One solution would be:

render() {
  return (
    <template
      dangerouslySetInnerHTML={{
        __html: '<div>some content</div>'
      }}
    />
  )
}

Now you're asking react to create all those children tags as node elements but letting the browser decide what to do with them.

Nicer solution

You might not want to use dangerouslySetInnerHTML all the time. So let's create a helper component:

function Template({ children, ...attrs }) {
  return (
    <template
      {...attrs}
      dangerouslySetInnerHTML={{ __html: children }}
    />
  );
}

Now any time you need to use a template you can use it like this:

render() {
  return (
    <Template>
      {'<div>some content</div>'}
    </Template>
  )
}

Don't forget to put the inner content in quotes, because it should be a string.

Marlow answered 16/3, 2017 at 21:35 Comment(4)
dangerouslySetInnerHTML won't work if the template contains scripts (which happens to be my use case).Catoptrics
I'm having the same issue! @Catoptrics were you find any solution?Blastocyst
I think I gave up, but the most promising path was Range.createContextualFragment() which does let the scripts run. Probably used with a Reactjs ref.Catoptrics
I solved it by wrapping the children in ReactDomServer.renderToStringBlastocyst
U
0

I know that this question has already an answer, but there is another, I guess simpler solution to do that creating hoc (Higher Order Component).

Just create new "component" like this:

// hoc/Template.js
const template = props => props.children
export default template

and then you can use it in your project this way:

import './hoc/Template.js'

...

render() {
  return (
    <Template>
      {'<div>some content</div>'}
    </Template>
  )
}

Newer version of react has already build it such a component, so you can achieve same thing without creating component.

import { Fragment } from 'react'

...

render() {
  return (
    <Fragment>
      {'<div>some content</div>'}
    </Fragment>
  )
}
Utricle answered 10/7, 2020 at 19:0 Comment(0)
R
0

This is actually a bug in React! https://github.com/facebook/react/issues/19932

When the native browser parses the <template> tag, it appends all children to the template's content fragment, instead of adding them as children. So a template should never actually have any children.

However, if you programmatically build the DOM (the way react-dom does), then the template will have an empty content fragment, since all children are added as children. This is why web components will not behave properly with these templates.

Here's an example of the problem too: https://codesandbox.io/s/new-sun-8l62o?file=/src/App.tsx:1056-1118

The easiest workaround is to use dangerouslySetInnerHTML, like so:

<template dangerouslySetInnerHTML={{ __html: `
  <p> Some text </p>
` }} />

This is limited by the fact that you can only supply raw HTML (no custom React elements). However, since you're using the <template> tag in the first place, it seems highly unlikely that you'd also be using React within the template.

Runnerup answered 9/2, 2022 at 21:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.