Skip to content

Instantly share code, notes, and snippets.

@mathieuancelin
Last active August 29, 2015 14:10
Show Gist options
  • Select an option

  • Save mathieuancelin/cca14d31184bf4468bc1 to your computer and use it in GitHub Desktop.

Select an option

Save mathieuancelin/cca14d31184bf4468bc1 to your computer and use it in GitHub Desktop.
Issue with UI events when rendering React component inside WebComponent

And the aswer is ...

because of the way Shadow DOM is handling events, React does not attach its listener on the right container (it attaches the listener on the owner document of the container instead of the container itself).

A pending PR will fix this behavior : facebook/react#1877 by patching src/browser/ui/ReactDOMComponent.js like the following :

-    var doc = container.nodeType === ELEMENT_NODE_TYPE ?
-      container.ownerDocument :
-      container;
+    var doc;
+    if (container.parentNode.hasOwnProperty('olderShadowRoot') || container.nodeType !== ELEMENT_NODE_TYPE) {
+      doc = container;
+    } else {
+      doc = container.ownerDocument;
+    }

React components working in shadow root

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Webcomponents issue</title>
<!--script src="node_modules/polymer-platform/dist/platform.js"></script-->
<link rel="import" href="./timer.html">
</head>
<body>
<awesome-timer></awesome-timer>
<less-awesome-timer></less-awesome-timer>
</body>
</html>

React + Shadow DOM issue

when I render a React component inside the shadow DOM of a webcomponent (<less-awesome-timer />) in this example, React doesn't seems to be able to catch UI event at all.

But if I render the React component inside the webcomponent node (<awesome-timer />), then everything is fine.

Components view inside the chrome inspector

React components behavior

{
"name": "react-webcomponent-issue",
"version": "1.0.0",
"private": true,
"dependencies": {
"react": "0.12.0",
"polymer-platform": "~0.3.6"
}
}
<script type="text/javascript" src="node_modules/react/dist/react.min.js"></script>
<script type="text/javascript">
(function(window, document, undefined) {
var registrationFunction = (document.registerElement || document.register).bind(document);
if (registrationFunction === undefined) {
console.error('No registerElement function !!!');
return;
}
function registerComponent(tag, reactClass) {
var ElementProto = Object.create(HTMLElement.prototype);
ElementProto.createdCallback = function() {
var reactiveElement = {};
var props = {};
for (var i in this.attributes) {
var item = this.attributes[i];
props[item.name] = item.value;
}
reactiveElement.props = props;
reactiveElement = React.createElement(reactClass, props);
this.renderedReactElement = React.render(reactiveElement, this);
};
ElementProto.attributeChangedCallback = function (attr, oldVal, newVal) {
var props = {};
props[attr] = newVal;
this.renderedReactElement.setProps(props);
}
registrationFunction(tag, {
prototype: ElementProto
});
}
function registerComponentInShadowDOM(tag, reactClass) {
var thatDoc = document;
var ElementProto = Object.create(HTMLElement.prototype);
ElementProto.createdCallback = function() {
var reactiveElement = {};
var props = {};
for (var i in this.attributes) {
var item = this.attributes[i];
props[item.name] = item.value;
}
reactiveElement.props = props;
reactiveElement = React.createElement(reactClass, props);
var shadowRoot = this.createShadowRoot();
var clone = thatDoc.createElement('div');
clone.setAttribute('id', 'reactcomponent');
shadowRoot.appendChild(clone);
this.renderedReactElement = React.render(reactiveElement, clone);
};
ElementProto.attributeChangedCallback = function (attr, oldVal, newVal) {
var props = {};
props[attr] = newVal;
this.renderedReactElement.setProps(props);
}
registrationFunction(tag, {
prototype: ElementProto
});
}
var Timer = React.createClass({
displayName: 'Timer',
getInitialState: function() {
return { secondsElapsed: 0 };
},
tick: function() {
this.setState({ secondsElapsed: this.state.secondsElapsed + 1 });
},
componentDidMount: function() {
this.interval = setInterval(this.tick, 1000);
},
componentWillUnmount: function() {
clearInterval(this.interval);
},
reset: function() {
console.log('Reset secondsElapsed');
this.setState({ secondsElapsed: 0 });
},
render: function() {
return (
React.createElement("div", null,
React.createElement("button", {type: "button", onClick: this.reset}, "Reset"),
React.createElement("div", null, "Seconds Elapsed: ", this.state.secondsElapsed)
)
);
}
});
registerComponent('awesome-timer', Timer);
registerComponentInShadowDOM('less-awesome-timer', Timer);
})(window, document);
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment