Skip to content

Commit 25caa22

Browse files
authored
Add docs for useDefault (#1396)
* Add docs for useDefault
1 parent 69d7e35 commit 25caa22

File tree

2 files changed

+64
-15
lines changed

2 files changed

+64
-15
lines changed
Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,44 @@
1-
import {LitElement, html, css} from 'lit';
1+
import {LitElement, html, css, PropertyDeclaration} from 'lit';
22
import {customElement, property} from 'lit/decorators.js';
33

44
@customElement('my-element')
55
class MyElement extends LitElement {
6-
@property({type: Boolean, reflect: true})
7-
active: boolean = false;
8-
6+
/* playground-fold */
97
static styles = css`
108
:host {
11-
display: inline-block;
9+
display: inline-block;
10+
padding: 4px;
1211
}
13-
1412
:host([active]) {
15-
border: 1px solid red;
13+
font-weight: 800;
14+
}
15+
:host([variant]) {
16+
outline: 4px solid green;
17+
}
18+
:host([variant="special"]) {
19+
border-radius: 8px; border: 4px solid red;
1620
}`;
21+
/* playground-fold-end */
22+
@property({type: Boolean, reflect: true})
23+
active: boolean = false;
24+
25+
@property({reflect: true, useDefault: true} as PropertyDeclaration)
26+
variant = 'normal';
1727

1828
render() {
1929
return html`
20-
<span>Active: ${this.active}</span>
21-
<button @click="${() => this.active = !this.active}">Toggle active</button>
30+
<div><label>active: <input type="checkbox"
31+
.value="${this.active}"
32+
@change="${(e: {target: HTMLInputElement}) =>
33+
this.active = e.target.checked}">
34+
${this.active}
35+
</label></div>
36+
<div><label>variant: <input type="checkbox"
37+
.value="${this.variant === 'special'}"
38+
@change="${(e: {target: HTMLInputElement}) =>
39+
this.variant = e.target.checked ? 'special' : 'normal'}">
40+
${this.variant}
41+
</label></div>
2242
`;
2343
}
2444
}

packages/lit-dev-content/site/docs/v3/components/properties.md

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,9 @@ class MyElement extends LitElement {
132132
In **JavaScript**, you **must not use class fields** when declaring reactive properties. Instead, properties must be initialized in the element constructor:
133133
```js
134134
class MyElement extends LitElement {
135-
static properties = {foo: {type: String}}
135+
static properties = {
136+
foo: {type: String}
137+
}
136138
constructor() {
137139
super();
138140
this.foo = 'Default';
@@ -269,6 +271,18 @@ When converting a string-valued attribute into a property, Lit's default attribu
269271

270272
When using TypeScript, this field should generally match the TypeScript type declared for the field. However, the `type` option is used by the Lit's _runtime_ for string serialization/deserialization, and should not be confused with a _type-checking_ mechanism.
271273

274+
</dd>
275+
<dt id="use-default">
276+
277+
`useDefault`
278+
279+
</dt>
280+
<dd>
281+
282+
Set to true to prevent initial attribute reflection for the default value when `reflect` is set to true, and to reset the property to its default value when its corresponding attribute is removed.
283+
284+
The default value is the property's initial value set in the constructor or with an [auto-accessor](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html#auto-accessors-in-classes). This value is retained in memory so it's a good practice to avoid setting `useDefault: true` for non-primitive Object/Array properties. For more information, see [Enabling attribute reflection](#reflected-attributes) and [Best practices when reflecting attributes](#best-practices-when-reflecting-attributes).
285+
272286
</dd>
273287

274288
Omitting the options object or specifying an empty options object is equivalent to specifying the default value for all options.
@@ -551,29 +565,44 @@ If this behavior doesn't fit your use case, there are a couple of options:
551565

552566
### Enabling attribute reflection {#reflected-attributes}
553567

554-
You can configure a property so that whenever it changes, its value is reflected to its [corresponding attribute](#observed-attributes). Reflected attributes are useful because attributes are visible to CSS, and to DOM APIs like `querySelector`.
568+
Setting `reflect` to true configures a property so that whenever it changes, its value is reflected to its [corresponding attribute](#observed-attributes). Reflected attributes are useful for serializing element state and because they are visible to CSS and DOM APIs like `querySelector`.
569+
570+
Setting `useDefault` to true prevents the property's default value from initially reflecting to its [corresponding attribute](#observed-attributes). All subsequent changes are reflected; and if the attribute is removed, the property is reset to its default value.
571+
572+
This matches web platform behavior for attributes like `id`. The default value of an element's `id` property is `''` (an empty string) and initially it does not have an `id` attribute, but if the `id` property is set (even to an empty string), the appropirate `id` attribute is reflected. If the `id` attribute is removed, the element's `id` property is set back to its initial value of `''`.
555573

556574
For example:
557575

558576
```js
559577
// Value of property "active" will reflect to attribute "active"
560578
active: {reflect: true}
579+
// Value of property "variant" will reflect except that the "variant"
580+
// attribute will not be iniitally set to the property's default value.
581+
variant: {reflect: true, useDefault: true}
561582
```
562583

563584
When the property changes, Lit sets the corresponding attribute value as described in [Using the default converter](#conversion-type) or [Providing a custom converter](#conversion-converter).
564585

565586
{% playground-example "properties/attributereflect" "my-element.ts" %}
566587

567-
Attributes should generally be considered input to the element from its owner, rather than under control of the element itself, so reflecting properties to attributes should be done sparingly. It's necessary today for cases like styling and accessibility, but this is likely to change as the platform adds features like the [`:state` pseudo selector](https://wicg.github.io/custom-state-pseudo-class/) and the [Accessibility Object Model](https://wicg.github.io/aom/spec/), which fill these gaps.
568-
569-
Reflecting properties of type object or array is not recommended. This can cause large objects to serialize to the DOM which can result in poor performance.
570-
571588
<div class="alert alert-info">
572589

573590
**Lit tracks reflection state during updates.** You may have realized that if property changes are reflected to an attribute and attribute changes update the property, it has the potential to create an infinite loop. However, Lit tracks when properties and attributes are set specifically to prevent this from happening
574591

575592
</div>
576593

594+
### Best practices when reflecting attributes {#best-practices-when-reflecting-attributes}
595+
596+
To ensure elements behave as expected and perform well, try to follow these best practices when reflecting attributes:
597+
598+
* Attributes should generally be considered input to the element from its owner, rather than under control of the element itself, so reflecting properties to attributes should be done sparingly. Consider instead using the [`:state` pseudo selector](https://wicg.github.io/custom-state-pseudo-class/) and the [Accessibility Object Model](https://wicg.github.io/aom/spec/) where possible.
599+
600+
* Reflecting properties should typically also set `useDefault: true` since this keeps the element from spontaneously spawning attributes that the user didn't set, and helps match expected platform behavior.
601+
602+
* Reflecting properties of type object or array is not recommended. This can cause large objects to serialize to the DOM which can result in poor performance and consume excess memory when `useDefault` is used.
603+
604+
* The property decorator does not alter any values assigned to the reactive property, which is considered a best practice for custom accessors. Sometimes native elements restrict properties to certain valid values, for instance, and if an invalid value is assigned to a property, the property will be set to a default instead. `useDefault: true` does not do this - it only restores the default when the attribute is removed. If you'd like to alter the property value on property assignments, define and decorate a custom property setter.
605+
577606
## Custom property accessors {#accessors}
578607

579608
By default, LitElement generates a getter/setter pair for all reactive properties. The setter is invoked whenever you set the property:

0 commit comments

Comments
 (0)