Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using React 15, under IE, dangerouslySetInnerHTML doesn't work on any SVG tag #6950

Closed
lgra opened this issue Jun 2, 2016 · 3 comments
Closed

Comments

@lgra
Copy link

lgra commented Jun 2, 2016

Hi,

Since React15, DOM reconciliation produce deep DOM manipulation instead of setting innerHTML of higher level html DOM node. Every DOM mutation is done exactly where it has to be done, either for HTML element or SVG element.
As a side effect, when you use dangerouslySetInnerHTML on any SVG element, React create the SVG element using document.createElementNS then set the innerHTML element of the new element. That's pretty cool on any recent browser, except IE. IE11 doesn't support innerHTML on any SVG tag. There is no script error as assigning an unknown property is always allowed on any Object. But nothing is added in the DOM tree.

for example, this stateless component produce an empty svg on IE:

var Icon = ({svg}) => {
  return (
    <svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" enableBackground="new 0 0 512 512" style={{width: "128px", height: "128px", fill: "red"}}>
      <title>a red icon</title>
      <g dangerouslySetInnerHTML={{__html: svg}}/>
    </svg>
  )
}

var svgPath = '<path d="M207.436,50v98.074h98.136V50H207.436z M364.429,148.074H462V50h-97.571V148.074z M148.578,148.074V50H50v98.074H148.578z M50,206.932h412V462H50V206.932z"/>'

ReactDOM.render(
  <Icon svg={svgPath} />,
  document.body
)

here is a codepen demonstrating this issue

The goal is to add a string containing a literal SVG path (svgPath in the code) into a g tag wrapper inside a svg generic React component.

The first red icon is created using React15.
The second green icon is created using direct DOM manipulation and setting innerHTML on a g tag.
The third blue icon is created using direct DOM manipulation, without using the innerHTML on a g tag.

Avery modern browser is able to display the 3 icons, except Internet Explorer (any version), which is only capable to display the blue one.

The blue one use a hack to not use innerHTML directly on the g tag.

  • embed the literal SVG path between svg literal tag
  • create a temporary div tag
  • set the innerHTML of this div tag
  • loop on any div.svg.childNodes and append them to the g tag.
var ieDivWrapper = document.createElement("div")
ieDivWrapper.innerHTML = "<svg>" + svgPath + "</svg>"
Array.prototype.forEach.call(ieDivWrapper.firstChild.childNodes, (child) => {
  ieElemG.appendChild(child)
})

Here is a way to know if the browser supports or not innerHTML on svg tag:

var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg")
var supportInnerHtml = svg.innerHTML !== undefined && !svg.hasOwnProperty("innerHTML")

A way to avoid this issue with current React 15 in a statefull component is to embed the hack describe here in the componentDidMount and eventually the componentDidUpdate methods.
Considering the exemple provided here, this statefull version is working:

class IconIE extends React.Component {
  static propTypes = {
    svg: React.PropTypes.string.isRequired
  };
  setInnerSvg() {
    var ieDivWrapper = document.createElement("div")
    ieDivWrapper.innerHTML = "<svg>" + this.props.svg + "</svg>"
    Array.prototype.forEach.call(ieDivWrapper.firstChild.childNodes, (child) => {
      this.refs.itemG.appendChild(child)
    })
  }
  componentDidMount() {
    this.setInnerSvg()
  }
  componentDidUpdate(prevProps) {
    if (prevProps.svg !== this.props.svg) {
      this.setInnerSvg()
    }
  }
  render() {
    return (
      <svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" enableBackground="new 0 0 512 512" style={{width: "128px", height: "128px", fill: "blue"}}>
        <title>a blue icon</title>
        <g ref="itemG" dangerouslySetInnerHTML={{__html: ""}}/>
      </svg>
    )
  }
}

ReactDOM.render(
  <IconIE svg={svgPath} />,
  document.getElementById('PATCHEDcontainer')
)

and the codepen.io

Please notes that it's partially a regression, as dangerouslySetInnerHTML on an svg tag was working before React15 at the initial render (the svgPath string was added to the virtual DOM string, the whole virtual DOM generated by the render method was added to the real DOM using innerHTML of one of the the parents node). It was not working on a props update, except setting a key props on the high level svg tag, forcing react to use innerHTML of its parent (I was doing that without knowing why. Now, I do know).

Regards.

@jimfb
Copy link
Contributor

jimfb commented Jun 2, 2016

cc @spicyj

@sophiebits
Copy link
Collaborator

Okay, we can change setInnerHTML to be along the lines of:

var reusableSVGContainer = document.createElement('div');

function setInnerHTML(node, html) {
  reusableSVGContainer.innerHTML = '<svg>' + html + '</svg>';
  var svg = reusableSVGContainer.firstChild;
  while (svg.firstChild) {
    node.appendChild(svg.firstChild);
  }
}

in the case of SVG and missing .innerHTML support (that is, only in IE). We should also create the container lazily.

sophiebits pushed a commit that referenced this issue Jun 8, 2016
* Workaround IE lacking innerHTML on SVG elements

* Add tests for setInnerHTML

* Correctly check if node has innerHTML property

* Ensure tests for setInnerHTML actually tests both codepaths

* Provide mock element for setInnerHTML tests

* Only use SVG setInnerHTML workaround for SVG elements
zpao pushed a commit to zpao/react that referenced this issue Jun 8, 2016
…ebook#6982)

* Workaround IE lacking innerHTML on SVG elements

* Add tests for setInnerHTML

* Correctly check if node has innerHTML property

* Ensure tests for setInnerHTML actually tests both codepaths

* Provide mock element for setInnerHTML tests

* Only use SVG setInnerHTML workaround for SVG elements

(cherry picked from commit 99d8524)
@lgra
Copy link
Author

lgra commented Jun 9, 2016

Sound fine, thank you!

zpao pushed a commit that referenced this issue Jun 14, 2016
* Workaround IE lacking innerHTML on SVG elements

* Add tests for setInnerHTML

* Correctly check if node has innerHTML property

* Ensure tests for setInnerHTML actually tests both codepaths

* Provide mock element for setInnerHTML tests

* Only use SVG setInnerHTML workaround for SVG elements

(cherry picked from commit 99d8524)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants