I have the following web component:
I originally tested this locally, (in a create-react-app) didn't work. I then put it into codesandbox (react template) where it doesn't work either.
class CounterWebComponent extends HTMLElement {
constructor() {
console.log("ctor");
super();
}
_count: number = 0;
set count(value: number) {
console.log("CounterWebComponent set", value);
this._count = value;
}
get count() {
console.log("CounterWebComponent get", this._count);
return this._count;
}
connectedCallback() {
console.log("CounterWebComponent connected");
this.innerHTML = "<h1>fff</h1>";
}
disconnectedCallback() {
console.log("CounterWebComponent disconnected");
}
}
if (!customElements.get("counter-wc-custom"))
customElements.define("counter-wc-custom", CounterWebComponent);
In index.html I'm trying to set the count:
<counter-wc-custom id="counter1"></counter-wc-custom>
<script>
const counter2 = document.getElementById("counter1");
counter2.count = 23;
alert(counter2.count);
alert(counter2._count); //using alert because console.log inside index.html is not working for some reason in codesandbox
console.log("count", counter2.count);
console.log("_count", counter2._count);
counter2.addEventListener("count", (e) => console.log(e));
</script>
If I use it like this it works:
const a = document.createElement("counter-wc-custom");
a.count = 2;
console.log("count", a.count);
console.log("_count", a._count);
EDIT: https://codesandbox.io/s/react-web-component-custom-wrapper-xh2n9s (I forgot the link like an idiot)
I assume I configured something somewhere incorrectly but I have no idea what. Any help is appreciated.
This has to do with timing; specifically, when you set the property versus when you define the custom element. Let's say we do:
<hello-world></hello-world>
<script>
const helloWorld = document.querySelector('hello-world');
helloWorld.message = 'Foo!'
customElements.define('hello-world', class extends HTMLElement {
#message = 'Default message.'
get message(){ return this.#message; }
set message(value){
this.#message = value;
console.log('Message: ', this.#message);
}
});
helloWorld.message = 'Bar?'
</script>
In this example, we set the message
property on the helloWorld
instance before the instance is defined. Then when we define the custom element, the getters and setters are defined on the element's prototype; but, the helloWorld
instance now already has an "own" property message
. This means even setting the message
to Bar?
after definition doesn't trigger the setter, because it's still just using the property initially defined on the instance, completely ignoring the getters and setters.
If we now delete helloWorld.message
, we remove that initially defined property, and then doing helloWorld.message = 'Baz!'
will finally trigger the setter.
You can actually check that this is the case by getting a reference to the custom element instance in question and seeing what the result is of Object.getOwnPropertyDescriptors(elementInstance)
. Any properties included in the result of that call are "overwriting" getters and setters you may have defined on the prototype, and so are likely unwanted.
In the example I gave above, the order things run in is (intentionally) obvious. In codesandbox, it might be a little less obvious; looking at the actual page it outputs it seems like it is indeed running your .count = 23
before it defines the component (and the above check indeed reveals count
to be among the ownPropertyDescriptors
).
The way I see it, there are a two main ways to solve this issue:
customElements.whenDefined(...)
.