Capture custom event dispatched from web component to React

191 views Asked by At

I need to embed a Lit web component in a React application. The web component at some point will be dispatching a custom event, and I need to capture it in the React application and react accordingly.

However, for some reason I am failing to capture the event. I have very little to no experience using React, so this is making it more difficult.

so I have my <custom-web-component/> embedded in the tsx file. Then I tried to register an event listener like below:

React.useEffect(() => {
    window.addEventListener("myCustomEvent", onCustomEvent);

    // cleanup this component
    return () => {
      window.removeEventListener("myCustomEvent", onCustomEvent);
    };
}, []);


function onCustomEvent(event: Event) {
    const customEventDetail = (event as CustomEvent).detail as EventDetail;

    console.log(customEventDetail);
}

However, when the event is triggered by the custom component, the event listener is not capturing it and hence I don't get anything logged in the console.

3

There are 3 answers

1
Michał Rokita On

The issue is that you listen and emit in different scopes. When you emit in a web component that does not "escape" the shadowDOM. You can do a couple of things, one is just putting an event handler on a component, which with this kind of component should work out of the box. You can listen for events on that component's shadowDOM or change event emitter config to pass through shadomDOM and be "accessible" on window.

If you set bubbles: true, composed: true when emitting, you should be able to do what you want.

But to be frank, with lit components. The easiest way is just to follow the documentation. What you want to achive should work out of the box as <my-lit-component onMyCustomEvent={handler} />.

Check out docs. here https://lit.dev/docs/frameworks/react/

0
mvallejo3 On

It is not clear from your question whether the react code that you posted lives inside a component or not. So to be extra clear, Here is an example of how that react file should look like:

import { useEffect } from "react";

// This is a react component (inside your react application) that listens to the custom event that gets fired by the web component.
const ComponentListeningToEvent = () => {

  // place the useEffect inside this component.
  useEffect(() => {
    // move this function iside the useEffect so you
    // don't have to add it as a dependency
    const onCustomEvent = (event: Event) => {
      const customEventDetail = (event as CustomEvent).detail as EventDetail;

      console.log(customEventDetail);
    }

    window.addEventListener("myCustomEvent", onCustomEvent);

    // cleanup this component
    return () => {
      window.removeEventListener("myCustomEvent", onCustomEvent);
    };
  }, []);

  // Your component does not have to return anything, 
  // so I left an empty fragment here.
  return <></>;
}

Then you just call the component inside your app where you want it.

<ComponentListeningToEvent />
0
Augustine Kim On

If you want to listen to custom event from outside of the shadow root, it's generally good that it be dispatched with both bubbles: true and composed: true options.

A better way to attach an event listener to the custom element would be to get a ref to it and add an event listener there, rather than on the document, if possible.


const Component = () => {
  // Provide the custom element class as type or use `HTMLElement`
  const ref = React.useRef<CustomWebComponent>(null);
  
  React.useEffect(() => {
    if (!ref.current) {
      return;
    }
    // You should declare the function inside the `useEffect` callback so the cleanup will have the same reference to it.
    function onCustomEvent(event: Event) {
      const customEventDetail = (event as CustomEvent).detail as EventDetail;

      console.log(customEventDetail);
    }
    ref.current.addEventListener('myCustomEvent', onCustomEvent);
    return () => {
      ref.current.removeEventListener('myCustomEvent', onCustomEvent);
    }
  }, []);

  return <custom-web-component ref={ref} />
}

Alternatively, you can use the @lit/react package to create a wrapping React component to your web component which allows you to write event handlers as props.