Suppose you create a very simple component in Stencil: a button component with a label attribute.
@Component({
tag: 'my-button',
styleUrl: './my-button.scss',
scoped: true
})
export class ButtonComponent {
@Prop() label: string = '';
render() {
return (
<button>{this.label}</button>
);
}
}
You can use it as <my-button label="button"></my-button>. On the initial render, render() runs with the @Prop in place, so the result is naturally displayed as <button>button</button>.
What happens if you change the label attribute in JavaScript after rendering?
const button = document.querySelector('my-button');
button.setAttribute('label', 'my-button')
Even after this change, my-button is not re-rendered. From the perspective of ButtonComponent, only the value of this.label changed, so render() does not run again. Stencil works this way to avoid the cost of watching every @Prop at the value level and to prevent unnecessary re-renders.
First, Stencil provides the @Watch decorator to detect changes. Let us update the component above.
@Component({
tag: 'my-button',
styleUrl: './my-button.scss',
scoped: true
})
export class ButtonComponent {
@Prop() label: string = '';
+ @Watch('label')
+ async watchLabelHandler() {
+ }
render() {
return (
<button>{this.label}</button>
);
}
}
Now, whenever label changes, watchLabelHandler() runs. However, render() still does not run. For render() to run, a property decorated with @State must change, so let us add a @State _label property for re-render tracking.
@Component({
tag: 'my-button',
styleUrl: './my-button.scss',
scoped: true
})
export class ButtonComponent {
@Prop() label: string = '';
+ @State() _label: string;
@Watch('label')
async watchLabelHandler() {
+ this._label = this.label;
}
render() {
return (
<button>{this.label}</button>
);
}
}
Now, whenever the label attribute changes, @Watch('label') runs watchLabelHandler(), and updating _label inside it triggers a re-render.
Simple, right?
See you next time.