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.
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.
You now understand connectedCallback in depth. Next, learn about the updated() hook that runs after every render.