javascriptfrontendlit-element

Trouble Syncing Checkbox State Between LitElement Property and DOM Attribute


Problem Description:

I'm using LitElement to create a controlled checkbox component. I'm facing an issue with synchronizing the checked attribute of the checkbox in the DOM with the checked property of my LitElement component.

Sample Code:

import { LitElement, html, css } from 'lit';

class ControlledCheckbox extends LitElement {
  static properties = {
    checked: { type: Boolean },
  };

  static styles = css`
    :host {
      display: block;
    }
  `;

  constructor() {
    super();
    this.checked = false;
    this.count = 0;
  }

  render() {
    return html`
      <div>${this.count}</div>
      <input
        type="checkbox"
        .checked="${this.checked}"
        @change="${this._handleChange}"
      />
    `;
  }

  _handleChange(event) {
    if (this.count > 5 && this.checked) {
      return;
    }
    this.count += 1;
    this.checked = event.target.checked;
  }
}

customElements.define('controlled-checkbox', ControlledCheckbox);

Problem:

The issue I'm encountering is that, despite implementing change event handling to update the checked property of my LitElement component, the checked attribute of the element in the DOM doesn't update correctly. As a result, the visual state of the checkbox doesn't always reflect the internal state of the component. Specifically, I expect the checkbox to remain checked (visually) after the count exceeds five and the checkbox is already checked.

What I've Tried:

-I've verified that the change event handling works correctly and that the checked property of the component updates properly.

-I've examined the LitElement documentation to understand if there's anything I'm doing wrong in handling properties and attributes.

Question:

Can anyone help me understand how I can properly synchronize the checked attribute of the checkbox in the DOM with the checked property of my LitElement component so that its visual state always reflects the internal component state? Thanks in advance for any suggestions or solutions!


Solution

  • I'm assuming you want the visual checkbox to stay filled, instead of the checked attribute on the <input> element which is only meant to indicate the default state.

    The user clicking the checkbox flips the internal checked property of the element. To override this behavior and to keep it checked, you need a Lit update cycle to happen immediately and make the .checked property binding stay true. This is usually done by setting the value on a reactive property but in your _handleChange() you are early returning without setting anything so no update happens by Lit. You can fix this by manually calling this.requestUpdate().

      _handleChange(event) {
        if (this.count > 5 && this.checked) {
          this.requestUpdate();
          return;
        }
        this.count += 1;
        this.checked = event.target.checked;
      }
    

    In addition to this, Lit's update will normally check whether it needs to update a binding by comparing against the last committed value, not the current value of the DOM. Since the checkbox changes its own state in the DOM, Lit won't know about this. To make sure Lit checks against the live DOM value rather than what it last committed, use the live() directive.

    import {live} from 'lit/directives/live.js';
    
      render() {
        return html`
          <div>${this.count}</div>
          <input
            type="checkbox"
            .checked="${live(this.checked)}"
            @change="${this._handleChange}"
          />
        `;
      }