The Hooks, in Order
Lightning Web Components run through a predictable lifecycle. Understand the order and you understand why the component behaves the way it does.
constructor()— the component class is instantiated.connectedCallback()— the element is inserted into the DOM.renderedCallback()— after each render cycle.disconnectedCallback()— the element is removed from the DOM.errorCallback(error, stack)— an error escapes from a child component.
Additionally:
render()— optional method returning the template to use (for dynamic template switching).
constructor()
The first thing that happens. The element exists but is not in the DOM yet. The component’s attributes and children have not been set.
Use for:
- Initializing properties with simple defaults.
- One-time class-level setup.
Don’t use for:
- Accessing
this.template— not ready yet. - Reading
@apiproperties — parent hasn’t set them yet. - Anything that touches the DOM.
Must call super() first.
connectedCallback()
Fires when the component is inserted into the DOM. @api properties are set at this point. You can access the component’s host element and parent, but not its own shadow DOM yet.
Use for:
- Registering event listeners (especially
window,documentlisteners). - Subscribing to Lightning Message Service.
- Starting intervals or timers.
- Emitting an initial event telling parents “I’m ready.”
- Imperatively calling Apex for data that doesn’t need
@wire.
Don’t use for:
- Setting
@apiproperties (parents own those). - Reading from
this.template.querySelector()— DOM isn’t ready.
A practical mistake: adding a document click listener in connectedCallback without removing it in disconnectedCallback. The listener persists after the component unmounts, leaking memory. Always pair.
renderedCallback()
Fires after every render. That includes the first render and every subsequent re-render. The shadow DOM is ready.
Use for:
- DOM measurements and adjustments that genuinely need to happen after render.
- Integrating with third-party JS libraries that need a DOM node.
- Scrolling to a position after content changes.
Don’t use for:
- Changing tracked state that causes another render — you’ll infinite-loop.
The infinite-loop protection:
renderedCallback() {
if (this.hasRendered) return;
this.hasRendered = true;
// one-time post-render work
}
For work that should happen on specific changes, track the last seen state and exit early if it hasn’t changed:
renderedCallback() {
if (this.lastProcessedId === this.recordId) return;
this.lastProcessedId = this.recordId;
// do work specific to this recordId
}
disconnectedCallback()
Fires when the element is removed. Cleanup time.
Use for:
- Removing event listeners added in
connectedCallback. - Unsubscribing from LMS.
- Canceling intervals (
clearInterval) and timeouts (clearTimeout). - Disconnecting from WebSocket / streaming subscriptions.
The most common memory leak in LWC comes from forgetting to clean up something added at connect time. If the component ends up orphaned (the parent re-renders and creates a new one), event handlers accumulate.
errorCallback(error, stack)
Catches errors thrown by descendants during their lifecycle. Your component acts as an error boundary.
Use for:
- Displaying a fallback UI when a child crashes.
- Logging errors to an error-tracking service.
- Resetting state after a child failure.
Don’t use for:
- Errors in async callbacks you made yourself (they don’t bubble here).
Pattern:
errorCallback(error, stack) {
this.error = error;
console.error('Child error:', error, stack);
}
In the template, show a user-facing error state when this.error is truthy.
render() (Optional)
By default, LWC uses the component’s .html template automatically. Override render() to return a different template at runtime.
Use for:
- Conditionally swapping entire templates (view mode vs. edit mode, for example).
- Advanced dynamic rendering.
Don’t use for:
- Toggling visibility inside a single template — use template directives instead (
lwc:if).
Use it sparingly. Dynamic template switching complicates debugging.
Order of Execution
For a parent-child relationship:
- Parent
constructor. - Parent
connectedCallback. - Child
constructor. - Child
connectedCallback. - Child
renderedCallback. - Parent
renderedCallback.
renderedCallback fires inside-out (child first, then parent). This matters when parent depends on knowing “all my children have rendered” — parent’s own renderedCallback is the safe signal.
Common Mistakes
Async work in constructor. Fires before @api props are set. You’ll reference this.recordId and get undefined.
Missing cleanup in disconnectedCallback. Event listeners and subscriptions leak. In testing harnesses this shows up as test pollution; in production, as bloat.
Infinite renderedCallback loops. Component changes state in renderedCallback, triggering another render, triggering renderedCallback again. Always guard.
Assuming DOM is ready in connectedCallback. It isn’t. Defer DOM access to renderedCallback.
Using lifecycle hooks as a substitute for reactive state. If you’re reaching for a hook to work around reactivity, rethink. Reactive properties (@track equivalents via object refs, or @api) solve most “I need to react to a change” problems.
Diagnostic Guide
If the component isn’t behaving:
- Not showing? Check
render()return value andlwc:ifconditions. - Data missing? Check
@wirestate. Data may not be loaded yet on first render. - Listener not firing? Confirm the listener is registered in
connectedCallback, notconstructor. Confirm the element is the right target. - Memory leak? Audit
disconnectedCallbackfor every resource created inconnectedCallback. - Multiple renders? Count
renderedCallbackfires. If excessive, find what state is changing and why.
Frequently Asked Questions
Do lifecycle hooks run in unit tests?
Yes — Jest-based LWC tests exercise the lifecycle. @salesforce/sfdx-lwc-jest provides the harness.
Can connectedCallback be async?
Technically it can be, but nothing awaits it. Async work fires; the lifecycle continues without waiting. Kick off promises and set state when they resolve.
Does renderedCallback fire on every keystroke?
Only if your template is re-rendering. Reactivity only triggers renders when tracked state actually changes. If your component re-renders on every keystroke unnecessarily, audit your state management.
Is there a hook for “all components on the page are ready”?
No. Each component knows its own state. Coordination is via events or LMS.