We've been looking at how to make the Elix web component library easier to use in a React application. We recently posted a React example to show using an Elix web component in a simple React app, but we can do more.
As noted on Custom Elements Everywhere, React currently has some well-known issues interacting with custom elements:
Based on our experience, I'd also add a third issue:
elix-carousel
instead of the class Carousel
.That's unfortunate. React components are referenced by class, and the inability to do the same with custom element classes makes them feel alien in the context of React. It also makes it harder to do proper linting and type-checking.
To use a web component in React today, you do something like the following:
// In custom-element.js
export default class MyCustomElement extends HTMLElement {}
customElements.define('my-custom-element', MyCustomElement);
// In app.jsx
import React from 'react';
import MyCustomElement from './custom-element.js';
class App extends React.Component {
render() {
return <my-custom-element></my-custom-element>;
}
}
The import MyCustomElement
statement will generate a lint error complaining that MyCustomElement
is unused, because it can't know that the string name my-custom-element
is an indirect reference to the MyCustomElement
class.
You could suppress the error by dropping the class name from the import:
import './custom-element.js';
But that simply masks the problem: there's no type-safe way to confirm the JavaScript code in the React app is interacting correctly with the custom element class.
It'd be preferable to refer to HTML custom elements by class just like one can with React component classes. Specifically, it'd be nice if JSX and the underlying React.createElement
could be extended to accept any subclass of the standard HTMLElement
base class:
// In custom-element.js
export default class MyCustomElement extends HTMLElement {}
customElements.define('my-custom-element', MyCustomElement);
// In app.jsx
import React from 'react';
import MyCustomElement from './custom-element.js';
class App extends React.Component {
render() {
return <MyCustomElement></MyCustomElement>; // This would be nice!
}
}
This would open up type safety and the attendant edit-time benefits of features like auto-complete and inline documentation.
In the meantime, we're considering maintaining React versions of the Elix web components that address some of the interop issues mentioned above. They let you listen to custom events raised by an Elix component using the standard React on
syntax. They also let you set properties using React-standard camelCase property names instead of hyphenated attribute names. (However, properties still only accept types that can be coerced to and from strings.)
Example:
import React from 'react';
import ListBox from 'elix-react/src/ListBox.jsx';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedIndex: 0
};
this.selectedIndexChanged = this.selectedIndexChanged.bind(this);
}
render() {
return (
<ListBox
onSelectedIndexChanged={this.selectedIndexChanged}
selectedIndex={this.state.selectedIndex}
></ListBox>
);
}
selectedIndexChanged(detail) {
const { selectedIndex } = detail;
this.setState({ selectedIndex });
}
}
Here's a simple demo of a React app using the React Elix components. This shows a React version of an Elix ListBox synchronized with an Elix Carousel.
As with the regular Elix web components, the React versions provide full keyboard, mouse, touch, and trackpad support, plus ARIA accessibility.
We're trying to decide if we should maintain the React versions of the Elix components on an ongoing basis. If that would be interesting to you, please tweet to the Elix project at @ElixElements.