Security News
Fluent Assertions Faces Backlash After Abandoning Open Source Licensing
Fluent Assertions is facing backlash after dropping the Apache license for a commercial model, leaving users blindsided and questioning contributor rights.
ce-la-react
Advanced tools
Create a React component from a custom element with SSR support.
Large parts of this package are copied from the
@lit/react
package.
The main difference is that this package is more geared towards the use of vanilla custom elements and support SSR in a simpler way.
Key differences are:
getTemplateHTML
createComponent
While React can render Web Components, it cannot easily pass React props to custom element properties or event listeners.
This package provides a utility wrapper createComponent
which makes a
React component wrapper for a custom element class. The wrapper correctly
passes React props
to properties accepted by the custom element and listens
for events dispatched by the custom element.
Since React v19 there is better support for custom elements, but unfortunately React wrappers are still needed for the time being.
For properties, the wrapper inspects the web component class to discover
its available properties. Next it differentiates from the original @lit/react
.
Then any React props
passed with property names are set on the custom element
as properties if there is no accomponying attribute name listed in
the static observedAttributes
array and if the property is not found in
the base class of the custom element. Primitive values are preferred as
attributes to support SSR (server side rendering).
For events, createComponent
accepts a mapping of React event prop names
to events fired by the custom element. For example passing {onfoo: 'foo'}
means a function passed via a prop
named onfoo
will be called when the
custom element fires the foo event with the event as an argument.
Import React
, a custom element class, and createComponent
.
import * as React from 'react';
import { createComponent } from 'ce-la-react';
import { MyElement } from './my-element.js';
export const MyElementComponent = createComponent({
tagName: 'my-element',
elementClass: MyElement,
react: React,
events: {
onactivate: 'activate',
onchange: 'change',
},
});
After defining the React component, you can use it just as you would any other React component.
<MyElementComponent
active={isActive}
onactivate={(e) => (isActive = e.active)}
/>
Event callback types can be refined by type casting with EventName
. The
type cast helps createComponent
correlate typed callbacks to property names in
the event property map.
Non-casted event names will fallback to an event type of Event
.
import * as React from 'react';
import { createComponent } from 'ce-la-react';
import { MyElement } from './my-element.js';
import type { EventName } from 'ce-la-react';
export const MyElementComponent = createComponent({
tagName: 'my-element',
elementClass: MyElement,
react: React,
events: {
onClick: 'pointerdown' as EventName<PointerEvent>,
onChange: 'input',
},
});
Event callbacks will match their type cast. In the example below, a
PointerEvent
is expected in the onClick
callback.
<MyElementComponent
onClick={(e: PointerEvent) => {
console.log('DOM PointerEvent called!');
}}
onChange={(e: Event) => {
console.log(e);
}}
/>
NOTE: This type casting is not associated to any component property. Be careful to use the corresponding type dispatched or bubbled from the webcomponent. Incorrect types might result in additional properties, missing properties, or properties of the wrong type.
props
to attributesFor advanced use cases, the props
to attributes conversion functions can be customized.
toAttributeName
- A function that converts the prop name to an attribute
name. This is useful when the attribute name is a different format than the prop name.
e.g. playbackId -> playback-idtoAttributeValue
- A function that converts the prop value to an attribute
value. This could be useful for serializing complex objects to strings.The default functions are:
export function defaultToAttributeName(propName: string) {
return propName.toLowerCase();
}
export function defaultToAttributeValue(propValue: unknown) {
if (typeof propValue === 'boolean') return propValue ? '' : undefined;
if (typeof propValue === 'function') return undefined;
if (typeof propValue === 'object' && propValue !== null) return undefined;
return propValue;
}
This package supports SSR by rendering the custom element template and setting
the React dangerouslySetInnerHTML
prop with the rendered template. The custom
element is then hydrated on the client side.
The only requirement is that the custom element class must provide a
static getTemplateHTML
method that returns the template HTML string. This
method is called with attributes that are converted from the React props
.
import * as React from 'react';
import { createComponent } from 'ce-la-react';
class MyProfile extends (globalThis.HTMLElement ?? class {}) {
static shadowRootOptions = { mode: 'open' };
static getTemplateHTML(attrs: Record<string, string>) {
return `<h1>Hello, ${attrs.firstname}!</h1>`;
}
static get observedAttributes() {
return ['firstname'];
}
#isInit = false;
// This init method might look strange but it is a pattern to avoid
// trying to access attributes in the constructor which is illegal!
//
// Just remember to call this method everywhere before you need to
// evaluate anything in the element's shadow DOM.
// Could be even in a property getter or setter.
#init() {
if (this.#isInit) return;
this.#isInit = true;
if (!this.shadowRoot) {
this.attachShadow({ mode: 'open' });
this.#render();
}
this.#upgradeProperty('firstName');
}
#render() {
if (this.shadowRoot) {
this.shadowRoot.innerHTML = MyProfile.getTemplateHTML({
...namedNodeMapToObject(this.attributes),
});
}
}
// This is a pattern to update property values that are set before
// the custom element is upgraded.
// https://web.dev/custom-elements-best-practices/#make-properties-lazy
#upgradeProperty(this: ElementProps<MyProfile>, prop: string) {
if (Object.prototype.hasOwnProperty.call(this, prop)) {
const value = this[prop];
// Delete the set property from this instance.
delete this[prop];
// Set the value again via the (prototype) setter on this class.
this[prop] = value;
}
}
connectedCallback() {
this.#init();
}
attributeChangedCallback(name: string, oldValue: string, newValue: string) {
this.#init();
if (oldValue === newValue) return;
if (name === 'firstname') {
this.#render();
}
}
get firstName() {
return this.getAttribute('firstname');
}
set firstName(value) {
if (value != null) this.setAttribute('firstname', value);
else this.removeAttribute('firstname');
}
}
if (globalThis.customElements && !globalThis.customElements.get('my-profile')) {
globalThis.customElements.define('my-profile', MyProfile);
}
const MyProfileComponent = createComponent({
react: React,
tagName: 'my-profile',
elementClass: MyProfile,
});
FAQs
Create a React component from a custom element.
The npm package ce-la-react receives a total of 41 weekly downloads. As such, ce-la-react popularity was classified as not popular.
We found that ce-la-react demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Fluent Assertions is facing backlash after dropping the Apache license for a commercial model, leaving users blindsided and questioning contributor rights.
Research
Security News
Socket researchers uncover the risks of a malicious Python package targeting Discord developers.
Security News
The UK is proposing a bold ban on ransomware payments by public entities to disrupt cybercrime, protect critical services, and lead global cybersecurity efforts.