Search code examples
reactjsstenciljs

StencilJS emit event to React


I have a nested set of StencilJS components. I would like to attach a function to my nested component so that my React app, which hosts the parent component, can read.

Example

<pw-actionbar
   actions={getActions}
/>

In this actionbar component, I have another nested button component. It looks like this

return (
  <Host>
    <div class="container">
      {
        // iterate through array
        this.actions.map((action) => {
          // take object.icon and make an icon
          const XmlIcon = `${action.icon}`;
          ==> I WANT A FUNCTION ON PW-BUTTON THAT PASSES 'action' which my react app reads
          return <pw-button-side-menu 
            // shade the selected pages button
            isselected={action.onpage ? 'selected' : 'notselected'}
            class="displace"
          >
            <span slot="label">{action.name}</span>
            <i slot="icon">
              <XmlIcon
                class="icon-position"
                fillcolor={this.iconfillcolor}
                strokecolor={this.iconstrokecolor}/>
            </i>
          </pw-button-side-menu>
        })
      }
    </div>
  </Host>
 );
}

My react app has some component

functionEmittedFromPwButton(action) {
  console.log(action) <=== I WANT THIS TO WORK IN MY REACT APP WHICH IS EMITTED FROM THE PW-BUTTON COMPONENT NESTED IN THE PW-ACTIONBAR COMPONENT
}
 return (
<MyComponent>
<pw-actionbar actions={getActions}/> <=== that takes an array of objects. I want to capture the 'action' object emitted by the pw-button nested in this component in my react app
</MyComponent>
)

I have tried all sorts of different methods like this one to try to emit the object from stencil to react


Solution

  • On the stenciljs side

      import { Component, h, Host, Prop, Event, EventEmitter } from "@stencil/core";
    @Component({
      tag: "pw-actionbar",
      styleUrl: "pw-actionbar.scss",
      shadow: true,
    })
    export class PwActionbar {
      @Prop() actions: any = [];
      @Prop() iconfillcolor: "white" | "black" = "white";
      @Prop() iconstrokecolor: "white" | "black" = "white";
      @Event() emitAction: EventEmitter;
    
      render() {
        const handleClick = (action) => {
          this.emitAction.emit(action);
        };
        return (
          <Host>
            <div class="container">
              {
                // iterate through array
                this.actions.map((action) => {
                  // take object.icon and make an icon
                  const XmlIcon = `${action.icon}`;
                  // cast the button
                  return (
                    <pw-button-side-menu
                      // shade the selected pages button
                      isselected={action.onpage ? "selected" : "notselected"}
                      class="displace button-lines"
                      onClick={() => handleClick(action)}
                    >
                      <span slot="label">{action.name}</span>
                      <i slot="icon">
                        <XmlIcon
                          class="icon-position"
                          fillcolor={this.iconfillcolor}
                          strokecolor={this.iconstrokecolor}
                        />
                      </i>
                    </pw-button-side-menu>
                  );
                })
              }
            </div>
          </Host>
        );
      }
    }
    

    On the react side

    const handleAction = async (action, history, i18n) => {
      Metrics.track("Changed Page", { action });
      if ("sign-out" === action) {
        await authActions.logout();
        history.push(`/${i18n.locale}`);
      } else if ("help-desk" === action) {
        history.push(`/${i18n.locale}/zendesk`);
      } else if ("advisors" === action) {
        pageActionsObjAdmin[0].onpage = true;
        history.push(`/${i18n.locale}/admin/advisors`);
      } else if ("users" === action) {
        pageActionsObjAdmin[1].onpage = true;
        history.push(`/${i18n.locale}/admin/users`);
      } else if ("forecast" === action) {
        pageActionsObjAdmin[3].onpage = true;
        history.push(`/${i18n.locale}/admin/forecast`);
      } else if ("stats" === action) {
        pageActionsObjAdmin[4].onpage = true;
        history.push(`/${i18n.locale}/admin/stats`);
      }
    };
    
    const Layout = ({ children }) => {
      const { i18n } = useLingui();
      const [, setContext] = useContext(StripeErrorContext);
    
      const history = useHistory();
    
      useEffect(() => {
        const listener = (e) => {
          // set page button to be "Active"
          pageActionsObjAdmin.forEach((element) => {
            element.onpage = false;
          });
          handleAction(e.detail.page, history, i18n, setContext);
        };
    
        // listen for events emitted form the action bar
        document.body.addEventListener("emitAction", listener);
    
        return () => {
          document.body.removeEventListener("emitAction", listener);
        };
      }, []); // eslint-disable-line
    
      // refs for the actionbar
      const elementRef = useRef(null);
      useEffect(() => {
        if (elementRef.current !== null) {
          elementRef.current.actions = pageActionsObjAdmin;
        }
      }, [elementRef]);
    
      return (
        <Wrapper>
          <Header />
          <BodyLayout>
            <pw-actionbar
              ref={(el) => (elementRef.current = el)}
              style={{ paddingTop: "56px", zIndex: "99" }}
              class="action-bar"
            />
            <div className="main-layout" style={{ width: "100%" }}>
              {children}
            </div>
          </BodyLayout>
        </Wrapper>
      );
    };
    
    export default Layout;