micaste
micaste

Reputation: 53

React synthetic events not working in Web Component

I followed React's documentation on how to use React inside of a Web Component (https://reactjs.org/docs/web-components.html) but found that the synthetic event system just does not work for the React tree inside of that React component.

Here is a JS Fiddle where you can try:

class CounterWithSyntheticEvents extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            counter: 0,
        };
    }

    handleClick() {
        this.setState(prevState => ({ counter: prevState.counter + 1 }));
    }

    render() {
        return (
            <div>
                Count {this.state.counter} <button onClick={this.handleClick.bind(this)}>+</button>
            </div>
        );
    }
}
class CounterWithDOMEvents extends React.Component {
    constructor(props) {
        super(props);
        this.buttonRef = React.createRef();
        this.state = {
            counter: 0,
        };
    }

    componentDidMount() {
        const listenerFn = this.handleClick.bind(this);
        this.buttonRef.current.addEventListener("click", listenerFn);
      this.removeListener = () => this.buttonRef.current.removeEventListener('click', listenerFn);
    }

    componentWillUnmount() {
        this.removeListener();
    }

    handleClick() {
        this.setState(prevState => ({ counter: prevState.counter + 1 }));
    }

    render() {
        return (
            <div>
                Count {this.state.counter} <button ref={this.buttonRef}>+</button>
            </div>
        );
    }
}

ReactDOM.render(<CounterWithSyntheticEvents />, document.getElementById("container"));

class ReactSyntheticEvents extends HTMLElement {
    connectedCallback() {
        const mountPoint = document.createElement("div");

        const shadow = this.attachShadow({ mode: "open" });
        shadow.appendChild(mountPoint);
        ReactDOM.render(<CounterWithSyntheticEvents />, mountPoint);
    }
}
customElements.define("react-synthetic-events", ReactSyntheticEvents);

class ReactDOMEvents extends HTMLElement {
    connectedCallback() {
        const mountPoint = document.createElement("div");

        const shadow = this.attachShadow({ mode: "open" });
        shadow.appendChild(mountPoint);
        ReactDOM.render(<CounterWithDOMEvents />, mountPoint);
    }
}

customElements.define("react-dom-events", ReactDOMEvents);
<div id="container"></div>
<react-synthetic-events ></react-synthetic-events>
<react-dom-events ></react-dom-events>

The approach with the manual listeners binding works, but I want to avoid rewriting every single react component that is to be integrated.

Does anyone know about a quick fix I could do in order to have events propagated to the components ? (to make the 2nd case of the fiddle work)

I have heard of Polymer-react but haven't given it a try yet. And I'd like to not introduce another layer if possible.

Upvotes: 3

Views: 2508

Answers (2)

micaste
micaste

Reputation: 53

I spent some time trying to find a fix or a workaround, and here is the result:

  • The bug is identified and a solution has been agreed upon: React should allow several listener roots, that should be the "containers" instead of the document only: https://github.com/facebook/react/issues/2043. There have been at least more than one attempt to fix this, but it seems like there has been no fix in the end, after more than two years : https://github.com/facebook/react/issues/2043#issuecomment-346039823. It looks like this won't be fixed unless we contribute, or Facebook starts looking into Web Components !
  • Some activity on StackOverflow (Click event not firing when React Component in a Shadow DOM) has led to the implementation of a workaround (https://www.npmjs.com/package/react-shadow-dom-retarget-events). The technical implementation can be summarized as a trial to replicate React's event handling system at the Web Component shadow-root-level, except that it is really simplified. When I tried it on a kind of form use-case, it worked for some UI components (checkbox, textfield), but not on some others (toggle). For the toggle (from Material UI), I was getting two events instead of one. In any-case, I find that trying to replicate React's even system with a couple lines of code is not a robust workaround.
  • Finally, I found a couple of libraries that claim to make the use of React easier with Web Components, but they are actually dealing with the "surface" of the Web Component instead of the inside of it, and therefore do not fix the issue. I tried https://github.com/skatejs/val and https://www.npmjs.com/package/web-react-components. I found the latter to be the most interesting (simple and to the point).

In the end, as a conclusion, we decided to use Web Components without the Shadow-DOM feature.

Upvotes: 2

HalfWebDev
HalfWebDev

Reputation: 7648

As per React docs.

Events emitted by a Web Component may not properly propagate through a React render tree. You will need to manually attach event handlers to handle these events within your React components.

So answer is you only can take help of attaching event listeners yourself.

Upvotes: 1

Related Questions