Question: Is there a way to propagate a property change to a child element without triggering its render
function? Currently when I update the property in parent's switchViewModeHandler
it triggers a re-render on the child.
Use-case: toggling the parent into 'edit' mode should toggle the same for all of its children as well.
Doubt: Should I use custom events? Problem is that it will be a complex network of nested elements, with events it will become unwieldy to debug pretty quickly (already ran into this issue with Polymer).
Setup: Parent element:
class ParentElement extends LitElement {
@property() viewMode;
constructor() {
this.viewMode = ViewMode.editable;
}
static get properties() {
return {
viewMode: { type: String, reflect: true },
};
}
private switchViewModeHandler(event: MouseEvent): void {
this.viewMode =
(this.viewMode === ViewMode.editing) ? ViewMode.editable : ViewMode.editing; // update my own edit mode
const options: HTMLElement[] = (Array.from(this.children) as HTMLElement[]);
options.forEach((item: HTMLElement) => {
item.viewMode = this.viewMode;
});
}
render() {
return html`
<p>parent</p><label class="switch-wrapper">toggle edit mode
<input type="checkbox"
?checked="${this.viewMode === ViewMode.editing}"
@click="${
(event: MouseEvent): void => this.switchViewModeHandler(event)
}"
/>
</label>
<slot></slot><!-- child comes from here -->
`;
}
}
Child element:
class ChildElement extends LitElement {
@property() viewMode;
constructor() {
super();
this.viewMode = ViewMode.editable;
}
static get properties() {
return {
viewMode: { type: String, reflect: true },
};
}
render() {
console.log('child render() called');
return html`
<div class="viewer">child viewer</div>
<div class="editor mode-${this.viewMode}">child editor</div>
`;
}
}
Markup:
<parent-element>
<child-element data-type="special"></child-element
></parent-element>
Edit mode comes from a simple enum that's imported (omitted here):
export enum ViewMode {
readOnly = 'readOnly',
editable = 'editable',
editing = 'editing',
}
Here's a Codesandbox to play with: https://codesandbox.io/s/v1988qmn75
Propagating state to light DOM children is a relatively rarely needed pattern, so we don't have it documented well yet, but there are some cases like yours where it's necessary.
The right approach really depends on how controlled the children of the container element are. If you, the author of the parent element, also are the only user of it, and so know exactly what the children can be, then you have some more flexibility.
Options I can think of are:
<child prop=${x}></child>
syntax available in lit-html.I'd say your approach to set child state in an event handler isn't far off, except that it's too tied to the user interaction event and not the state itself.
I'd go for something where you always update state in updated()
:
class ParentElement extends LitElement {
@property({ type: String, reflect: true })
viewMode = ViewMode.editable;
private _onSwitchClicked(event: MouseEvent): void {
this.viewMode = (this.viewMode === ViewMode.editing)
? ViewMode.editable
: ViewMode.editing;
}
private _onSlotChange() {
this.requestUpdate();
}
updated() {
for (const child of Array.from(this.children)) {
child.viewMode = this.viewMode;
}
}
render() {
return html`
<p>parent</p>
<label class="switch-wrapper">toggle edit mode
<input type="checkbox"
?checked=${this.viewMode === ViewMode.editing}
@click=${this._onSwitchClicked}>
</label>
<slot @slotchange=${this._onSlotChange}></slot>
`;
}
}
If the children are LitElements and the values are primitives, it's ok to always set the properties - they'll be dirty checked in the children.
Note the slotchange
event handler on <slot>
so we can observe the children.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With