Skip to content

connectedCallback

connectedCallback() is the native Custom Elements hook that fires whenever your component is inserted into the DOM. This lesson covers exactly when it runs and how to use it safely in Lit.

When connectedCallback Runs

connectedCallback() fires every time your element becomes connected to a document — the first time it's inserted, and again if it's removed and later re-inserted (for example, when moved between two containers). This is a native Custom Elements lifecycle callback that Lit's ReactiveElement extends, not something Lit invents itself.

Because it can run more than once per element instance, any setup you perform here (subscriptions, event listeners, intervals) should have a matching teardown in disconnectedCallback(), so re-connecting doesn't create duplicate subscriptions.

class LiveClock extends LitElement {
  @state() private now = new Date();
  #intervalId;

  connectedCallback() {
    super.connectedCallback();
    this.#intervalId = setInterval(() => {
      this.now = new Date();
    }, 1000);
  }

  disconnectedCallback() {
    super.disconnectedCallback();
    clearInterval(this.#intervalId);
  }

  render() {
    return html`<time>${this.now.toLocaleTimeString()}</time>`;
  }
}

The interval started in connectedCallback is always cleared in the matching disconnectedCallback, preventing a leaked timer if the clock is removed from the page.

connectedCallback Signature

connectedCallback() {
  super.connectedCallback(); // required — runs Lit's own setup
  // your setup: subscriptions, listeners, initial fetch, etc.
}
  • Always call super.connectedCallback() first — it performs Lit's own internal connection setup.
  • This hook runs before the first render() call, but properties set before connection are already available.
  • this.shadowRoot exists by this point, but it may still be empty until the first render completes.
  • Fetching initial data here (rather than in the constructor) is the standard Lit pattern for data-driven components.

connectedCallback Use Cases

Common setup tasks that belong in connectedCallback.

Task Why Here
Fetch initial data Runs once the element is actually in the document
Subscribe to a store/observable Pairs cleanly with disconnectedCallback unsubscribe
Add a global event listener (window, document) Must be added/removed per connection to avoid leaks
Start a setInterval/requestAnimationFrame loop Should only run while the element is actually visible/connected
Register with a parent/ancestor element Standard pattern for compound components

constructor() vs connectedCallback()

The constructor runs once, when the element instance is created — which can happen before it's ever inserted into the document (for example, document.createElement('my-el') without appending it). connectedCallback only runs once the element is actually part of a live document, which is the right time for anything that depends on the element genuinely being on the page.

Task constructor() connectedCallback()
Set default property values Yes Also fine, but constructor is more conventional
Fetch data from an API No — element may never be attached Yes
Read this.getBoundingClientRect() No — not laid out yet Yes, though firstUpdated is often better
Subscribe to global events No Yes

Adding and Removing Global Listeners Safely

Listeners on window or document (unlike listeners bound inside your own template) must be added and removed manually, since they aren't scoped to your element's own DOM. Bind the handler once as a class field or private method so the exact same function reference can be passed to both addEventListener and removeEventListener.

#onResize = () => { this.width = window.innerWidth; };

connectedCallback() {
  super.connectedCallback();
  window.addEventListener('resize', this.#onResize);
}

disconnectedCallback() {
  super.disconnectedCallback();
  window.removeEventListener('resize', this.#onResize);
}

Using an inline arrow function directly in removeEventListener would fail silently, since it's a different function reference than the one originally added.

Common Mistakes

  • Forgetting super.connectedCallback(), which can break Lit's internal reactive setup for the element.
  • Fetching data or reading layout information in the constructor instead of connectedCallback, before the element is actually attached.
  • Adding a global window/document listener without removing it in disconnectedCallback, leaking listeners on repeated connect/disconnect cycles.
  • Using a fresh inline arrow function for both add and remove, which fails to actually remove the original listener.

Key Takeaways

  • connectedCallback() runs every time the element is inserted into a live document, potentially more than once.
  • Always call super.connectedCallback() first when overriding it.
  • It's the right place for data fetching, subscriptions, and global event listeners.
  • Every setup performed here should have matching teardown logic in disconnectedCallback().

Pro Tip

If your component might be moved between containers (drag-and-drop, tab reparenting, virtualization), test that behavior explicitly — it's the scenario that most reliably exposes a missing or incorrect disconnectedCallback cleanup.