React Component Wrappers

Updated:

This feature is experimental within the Stencil Library (Docs) and is automated within their build process. Please report any issues encountered. However, note that the Tecton team does not maintain this specific tool.

This tutorial assumes your codebase is registering the Web Components via connect from the q2-tecton-sdk, or from the q2-design-system library (Guide).

If you're using React to build out your feature or application, then you can make use of the React component wrappers that are published as a part of Tecton.

This gives you a number of benefits including:

  • Removes the need to use refs for adding event listeners to Web Components
  • Autocompletion
  • Better event handling
  • Type safety

Getting started

Making use of the React component wrappers only requires a couple of steps.

First, you will need to install the framework wrappers library:

  • NPM: npm i q2-tecton-framework-wrappers
  • Yarn: yarn add q2-tecton-framework-wrappers

Using the components

Once the package is installed you can start using the components by importing the ones you need, just like you would with any other components. They are all located in q2-tecton-framework-wrappers/dist/react.

import { Q2Btn, Q2Input, Q2Section } from "q2-tecton-framework-wrappers/dist/react";

function Example() {
  const onFullNameInput = (event) => {
    const {
      detail: { value },
    } = event.nativeEvent;
    console.log(value);
  };

  return (
    <Q2Section label="My Section" collapsible expanded>
      <Q2Input label="Full name" optional onInput={onFullNameInput}></Q2Input>
      <Q2Btn intent="workflow-primary">Submit</Q2Btn>
    </Q2Section>
  );
}

export default Example;

Working with events

React currently faces a few challenges when dealing with Web Component attributes/properties and events. While React component wrappers have addressed the property-related issues, event-related issues persist. This primarily results from React's employment of synthetic events (Docs) for standard event names such as "change" and "input" (See a full list on MDN). As a result, setting up event listeners in your JSX with properties like onInput or onChange may not function as anticipated.

The React team is actively working on this issue and has provided a solution in their experimental branch, although it is not yet widely available.

In the interim, we suggest utilizing one of the two following workarounds:

Using refs

This method, although somewhat awkward, is effective for all event types. It involves the following steps:

  1. Create a ref for the web component you want to add an event listener to, using useRef.
  2. In a useEffect hook, use the ref to add your desired event listener(s) to the element.
  3. In the useEffect hook's return function, ensure that the event listener(s) are removed when the component is destroyed.

Here is a complete, functional example that demonstrates this approach:

import { Q2Input } from "q2-tecton-framework-wrappers/dist/react";
import { useEffect, useRef } from "react";

function MyComponent() {
  const inputRef = useRef<HTMLQ2InputElement>(null);

  useEffect(() => {
    const input = inputRef.current!;
    const onInput = (event: Event) => {
      const newEvent = event as CustomEvent;
      console.log("onInput", newEvent.detail);
    };

    input.addEventListener("input", onInput);

    return () => {
      input.removeEventListener("input", onInput);
    };
  }, []);

  return <Q2Input ref={inputRef} label="Name" />;
}

export default MyComponent;

Lowercase attributes

React's Synthetic Events are defined solely for native events (like input, change, and so on). Because React employs camel-case naming for its Synthetic Event listeners, you can bypass the problem by using original lowercase event listener attributes.

While this is a more straightforward solution, your linter may flag your use of lowercase attributes in React, hence the use of @ts-ignore in the example provided.

Additionally, if you're adding an event listener for a non-native event name, use the standard camel case format you're accustomed to in React (e.g., onClear in the example below).

Here is what that would look like:

import { Q2Input } from "q2-tecton-framework-wrappers/dist/react";

function MyComponent() {
  return (
    <Q2Input
      label="Name"
      
      // @ts-ignore
      oninput={(event: CustomEvent) => {
        console.log("oninput", event.detail.value);
      }}
      onClear={(event: CustomEvent) => {
        console.log("onClear", event.detail.value);
      }}
    />
  );
}

export default MyComponent;

We apologize for any inconvenience this situation might cause and look forward to an upcoming release from the React team to address this issue.