You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I'd like to propose the following pattern for Typescript types (which may be better suited going into the Helix repo, directly) and component references (which is most appropriate in this project)
Objective
A React component may contain many Helix web components within. It will often be necessary to access the methods and properties of those web components to manage their state from within a stateful React component (it's not possible for stateless components)
Defining strong types
In order to use Web Components in React with Typescript, they must be defined as JSX intrinsic elements. This means then need types to define all the properties to register with JSX. However, JSX only knows about the attributes, while when used within the component, the user may also need to access the methods and/or properties. This means two types will be needed (because JSX will complain, for example, if a web component has a method defined, but it's not passed in as an attribute when used)
Example Web Component Type
// HXTabset is the intrinsic type for JSX, only including attributes
// WebComponent is a custom type defining HTML Element attributes, like 'class'
interface HXTabset extends WebComponent {
// All attributes go here in kebab case
'current-tab'?: number;
}
// HXTabsetElement is for using the web component instance within
// React Component lifecycle hooks and instance methods
interface HXTabsetElement extends HTMLElement {
// All methods and properties go here
currentTab: number;
selectNext: Function;
}
Accessing web component instance
In order for a React component to access the instance of a child web component, it must have access to a reference. JSX recognizes a universal ref property that allows for obtaining that.
class MyReactComponent extends React.Component<MyProps> {
render() {
return (<hx-tabset ref={ /* access the ref here */} />)
}
}
There are a number of options to obtain the ref: a string literal which will be added to this.refs as a generic React.ReactInstance type, via a callback, or using React.createRef. The createRef method offers the best solution, as it is a generic which will allow us to type it in one place instead of anywhere we use the reference. We can add the ref as a class field and pass the field reference to the ref property to assign it.
class MyReactComponent extends React.Component<MyProps> {
// Use the Element type to access the methods and properties
private tabsetRef = React.createRef<HXTabsetElement>();
render() {
// Assign the element web component instance to the empty React component instance
return (<hx-tabset ref={this.tabsetRef} />)
}
}
This reference will contain a current member which yields access to the web component instance directly. It's worth note that this reference will initially be null until the React component mounts (which causes the first render, which assigns the reference), so anytime the web component is referenced, Typescript will insist that it may be null. This means in order to use a method on the web component instance, it must either be gated by a null check or non-null assertion, cast to the web component type directly, or defaulted on each use. This can be cumbersome, so a shortcut is recommended.
class MyReactComponent extends React.Component<MyProps> {
private tabsetRef = React.createRef<HXTabsetElement>();
// Create a getter to avoid cumbersome boilerplate
get tabset() {
// Use a non-null assertion (!) so TS doesn't complain
return this.tabsetRef.current!;
}
componentDidMount() {
// Now `tabsetRef` is on the instance of the React component, properly typed, and any
// of the web component instance methods and properties can be used
this.tabset.addEventListener(...);
this.tabset.selectNext();
}
}
One final note: ideally, non-null assertion usage should be avoided. However, this is the cleanest implementation. It is assured that none of the instance methods can be executed until the component has mounted at which point the reference will definitely exist.
The text was updated successfully, but these errors were encountered:
I'd like to propose the following pattern for Typescript types (which may be better suited going into the Helix repo, directly) and component references (which is most appropriate in this project)
Objective
A React component may contain many Helix web components within. It will often be necessary to access the methods and properties of those web components to manage their state from within a stateful React component (it's not possible for stateless components)
Defining strong types
In order to use Web Components in React with Typescript, they must be defined as JSX intrinsic elements. This means then need types to define all the properties to register with JSX. However, JSX only knows about the attributes, while when used within the component, the user may also need to access the methods and/or properties. This means two types will be needed (because JSX will complain, for example, if a web component has a method defined, but it's not passed in as an attribute when used)
Example Web Component Type
Accessing web component instance
In order for a React component to access the instance of a child web component, it must have access to a reference. JSX recognizes a universal
ref
property that allows for obtaining that.There are a number of options to obtain the ref: a string literal which will be added to
this.refs
as a genericReact.ReactInstance
type, via a callback, or usingReact.createRef
. ThecreateRef
method offers the best solution, as it is a generic which will allow us to type it in one place instead of anywhere we use the reference. We can add the ref as a class field and pass the field reference to theref
property to assign it.This reference will contain a
current
member which yields access to the web component instance directly. It's worth note that this reference will initially be null until the React component mounts (which causes the first render, which assigns the reference), so anytime the web component is referenced, Typescript will insist that it may benull
. This means in order to use a method on the web component instance, it must either be gated by a null check or non-null assertion, cast to the web component type directly, or defaulted on each use. This can be cumbersome, so a shortcut is recommended.One final note: ideally, non-null assertion usage should be avoided. However, this is the cleanest implementation. It is assured that none of the instance methods can be executed until the component has mounted at which point the reference will definitely exist.
The text was updated successfully, but these errors were encountered: