I am converting a React component I built into a Stencil web component, and I am unsure of how to retrieve all the props passed into the component that weren't defined with the @Prop decorator. Here is my React code:
import { ButtonHTMLAttributes } from "react";
export default function CoreButton({
name,
children,
...props
}: ButtonHTMLAttributes<HTMLButtonElement>) {
return (
<button
name={`example ${name}`}
{...props}
>
{children}
</button>
);
}
And here is conceptually how I want my Stencil code to work:
import { Component, Prop, h } from '@stencil/core';
@Component({
tag: 'core-button',
})
export class CoreButton {
@Prop() name: string;
render() {
return (
<button name={`example ${this.name}`} {...this.restProps}>
<slot />
</button>
);
}
}
I want the ability to extend any prop that would normally be able to be passed into , intercept the ones I want to add custom logic too by declaring them with @Prop and then spread the remaining props onto the actual element without hard coding 100s of attributes per custom component. Thanks.
Nope. That is not possible. Web components are bit more convoluted that traditional React components.
Web Component is basically an enhanced HTMLElement
So if you try to spread the DOM element, you get nothing:
const a = document.createElement('div');
a.tabIndex = 1;
const b = { ...a } ;
// Output: {}
So, in your case doing {...this}
is meaningless and also {...this.restProps}
means nothing since the property
restProps
doesn't exist on your custom element.
Now declaring a property using @Prop
decorator is doing two things.
render
is automatically triggered.property
and attribute
(These are two different things. In react worlds, attributes do not exist. Everything is prop).Take example of you core-button
component; you can create a component as shown below and try to add count
property to the component. But since, the count
is not declared as a prop, Stencil will not be able to figure out if it should trigger the render
method.
// Custom element
const a = document.createElement('core-button');
a.count= 10;
As a workaround, you can collect all the properties into a plain object using some function and then use that in your render method like:
import { Component, Prop, h } from '@stencil/core';
@Component({
tag: 'core-button',
})
export class CoreButton {
@Prop() name: string;
@Prop() count: number;
@Prop() disabled: boolean;
render() {
const restProps = this.collect();
return (
<button name={`example ${this.name}`} {...restProps}>
<slot />
</button>
);
}
collect() {
return {
count: this.count,
disabled: this.disabled
};
}
}
You can take it one step further by creating a automatic helper to achieve this. Use the package like reflect-metadata
and read all the reflection information for the Prop
decorator set on your CoreButton
class and then read those properties excluding the one that you don't need. You can then use this function in every component where you need this.