Search code examples
javascriptweb-componentlit-element

LitElement web component form event target set to null


I have an event handler for a form submit that sends the data in an HTTP request, then once a response is received it resets the form. However, it seems that the target property on the event object is initially available, but is quickly set to null. The below example is my attempt to distill the problem down into a minimal example (in order to help make it reproducible, I have used setTimeout in place of an HTTP request). It shows that while the event object initially has the form element as it's target property, after waiting for some period, the target property gets set to null. This is preventing me from resetting the form after the HTTP request is complete.

import { LitElement, css, html } from "lit-element";
import { render } from "lit-html";

class TestElement extends LitElement {
  submitForm(e) {
    e.preventDefault();
    console.log(e.target); // successfully logs <form> element
    window.setTimeout(() => {
      console.log(e); // logs event, with target now set to null
      e.target.reset(); // "Uncaught TypeError: Cannot read property 'reset' of null"
    }, 2000);
  }

  render() {
    return html`<form @submit=${this.submitForm}>
      <div class="form-controls">
        <label for="name">Name</label>
        <input type="text" id="name" name="name" />
        <label for="address">Address</label>
        <input type="text" id="address" name="address" />
        <button type="submit">submit</button>
      </div>
    </form>`;
  }
}
customElements.define("test-element", TestElement);
render(html`<test-element></test-element>`, window.document.body);

Solution

  • The problem is that webcomponent events are "retargeted" which means that once the event bubbles out of the webcomponent into the light DOM, the target property of events gets updated to be the webcomponent itself.

    To avoid this problem, don't use event.target and instead query the element directly for example with this.shadowRoot:

    class TestElement extends LitElement {
      submitForm(e) {
        e.preventDefault();
        const form = this.shadowRoot.querySelector("form");
        console.log(e.target, form); // successfully logs <form> element
        window.setTimeout(() => {
          console.log(form); // successfully logs <form> element
          form.reset(); // resets form
        }, 2000);
      }
    
      render() {
        return html`<form @submit=${this.submitForm}>
          <div class="form-controls">
            <label for="name">Name</label>
            <input type="text" id="name" name="name" />
            <label for="address">Address</label>
            <input type="text" id="address" name="address" />
            <button type="submit">submit</button>
          </div>
        </form>`;
      }
    }
    customElements.define("test-element", TestElement);