Just for some context, I recently got (temporarily) involved in a new React project that uses a third-party lib built on top of Polymer.

Since the whole rest of the project is in React we built some “adapters” for the web components, which makes them easier to use and encapsulates anything “weird” we might want to do:

// the component's .js is imported directly
// from index.html, since the lib is distributed
// in a single .js file
class CustomPane extends Component {
  render() {
    return (
      <custom-pane
       { ...this.props }
      >
        { this.props.children }
      </custom-pane>
    );
  }
}

Don’t get me wrong, web components are awesome. The use os Shadow DOM to encapsulate the implementation is great. This pane, however, has a nasty surprise: its width is hardcoded (in px)… in a style element, inside the Shadow DOM. Meaning you can’t override it with a global style - in web components styles are scoped and, even though they inherit from the parent, the child’s style will always override it if set.

ProTip: Polymer components allow theming with CSS properties. If you intend for others to use your components, please expose some properties to make their lifes easier.

Of course, in our case that wasn’t an option - we can’t modify the source of the lib. So what now? Well, lucky us, Polymer (by default) creates an open shadow root, so you can just… append some CSS, and overwrite what’s there!

We start by creating a Ref, so the actual DOM element is available in our React component:

class CustomPane extends Component {
+  constructor(props) {
+    super(props);
+    this.element = React.createRef();
+  }

  render() {
    return (
      <custom-pane
+      ref={this.element}
       { ...this.props }
      >
        { this.props.children }
      </custom-pane>
    );
  }
}

We need to access the element after it has been created on the DOM, so we use the proper lifecycle method to do it. We also want to add a new style node to overwrite the class that’s being used:

class CustomPane extends Component {
  constructor(props) {
    super(props);
    this.element = React.createRef();
  }

+ componentDidMount() {
+   const { style } = this.props;
+   const properWidth = style && style.width;
+   if (properWidth) {
+     const newStyle = document.createElement('style');
+     newStyle.innerHTML = `.pane-wrapper { width: ${properWidth} !important; }`;
+     const host = this.element.current;
+     host.shadowRoot.appendChild(newStyle);
+   }
+ }

  render() {
    return (
      <custom-pane
       ref={this.element}
       { ...this.props }
      >
        { this.props.children }
      </custom-pane>
    );
  }
}

We get the wanted width from our props, append it to the Shadow DOM… so far, so good, right? But there’s a catch: At this moment, we are still inside React’s lifecycle. The HTMLElement “custom-pane” has been created on the DOM, sure, but it still hasn’t been actually rendered by Polymer - so it has no shadow root for us to use. For that to happen, we have to wait until everything is done on this tick and append our style on the next one:

class CustomPane extends Component {
  constructor(props) {
    super(props);
    this.element = React.createRef();
  }

  componentDidMount() {
    const { style } = this.props;
    const properWidth = style && style.width;
    if (properWidth) {
+     setTimeout(() => {
        const newStyle = document.createElement('style');
        newStyle.innerHTML = `.pane-wrapper { width: ${properWidth} !important; }`;
        const host = this.element.current;
        host.shadowRoot.appendChild(newStyle);
+     }, 0);  
    }
  }

  render() {
    return (
      <custom-pane
       ref={this.element}
       { ...this.props }
      >
        { this.props.children }
      </custom-pane>
    );
  }
}

And that’s it, we’re finally done! We’ve now successfully customized a Polymer web component with hardcoded CSS properties! (:

This is what us brazilians usually call a “gambiarra”. If you are writing web components, please don’t force people to do it… allow customization points where appropriate!