javascripttypescriptevent-handlingweb-componentlit

Lit property expression is not updated even though render function is called with the correct value


I encountered some strange problem where a Lit Web component with Property Expression (.checked) does not get updated. Here is a simple reproduction at Lit Playground:

import {html, css, LitElement} from 'lit';
import {customElement, property} from 'lit/decorators.js';

@customElement('simple-greeting')
export class SimpleGreeting extends LitElement {
  static styles = css`p { color: blue }`;

  @property()
  selected = false;

  render() {
    debugger;
    return html`<input type="checkbox" 
        .checked=${this.selected}
        @change=${this.handleChange} />`;
  }
  
  handleChange(e) {   
    this.selected = e.target.checked;
    this.dispatchEvent(new Event("change"));
  }
}

And the HTML:

  <simple-greeting></simple-greeting>
  
  <script>
    const chk = document.querySelector("simple-greeting");
    chk.addEventListener("change", () => {
      console.log(chk.selected);
      chk.selected = false;
    });
  </script>

Here, every time I click on the checkbox, the value is true, false, true, false, true, false, ... instead of true all the time because it is supposed to revert back to false by the code. (In case anyone asks me why I do this, in real scenario, my checkbox is reverted if certain condition is not met).

When debugging, render() is called correctly and the value for selected is false (which is correct). However the result DOM element still has its checked being true half of the time.

enter image description here

Above screenshot: selected is false but the rendered checkbox ($0 is pointing to the checkbox, not the component) $0.checked is still true.

What is the issue here?

EDIT to add in case it helps figuring out the problem easier, if I update the property in a separate frame instead, the problem does not happen:

requestAnimationFrame(() => chk.selected = false);

Solution

  • As mentioned in the lit documentation

    If you want to overwrite the DOM value with the bound value no matter what—use the live() directive

    import {html, css, LitElement} from 'lit';
    import {customElement, property} from 'lit/decorators.js';
    import {live} from 'lit/directives/live.js';
    
    @customElement('simple-greeting')
    export class SimpleGreeting extends LitElement {
      static styles = css`p { color: blue }`;
    
      @property()
      selected = false;
    
      render() {
         console.log(this.selected,'Render');
        return html`<input type="checkbox" 
            .checked=${live(this.selected)}
            @change=${this.handleChange} />`;
      }
      
      handleChange(e) {   
       
        this.selected = e.target.checked;
        this.dispatchEvent(new Event("change"));
      }
    }
    

    Working Example