constructor = born. connectedCallback = moved into the page. renderedCallback = painted on the screen. disconnectedCallback = removed from the page. Always put your code in the earliest stage that has the data you need to run it.
⚡ 1-Minute Summary (Key Points)
- Never read
@apivalues in the constructor; they don't exist yet. - Use
connectedCallbackto set up subscriptions and run initial data fetches. renderedCallbackfires repeatedly after every single re-render. Always use a boolean guard to prevent infinite loops.- Always clean up event listeners and subscriptions inside
disconnectedCallbackto prevent memory leaks.
A developer tries to set up a chart library inside
connectedCallback. It breaks immediately because the canvas element doesn't exist in the DOM yet. The solution is to move the setup to renderedCallback using a hasRendered boolean guard so it only runs once. Finally, remove the resize listener in disconnectedCallback to clean up.
⏱️ The Exact Order of Operations
When an LWC is inserted into a page, it follows a strict sequence:
- 1. constructor: Runs first. There is no DOM yet, and no
@apivalues have been passed down from the parent. - 2. connectedCallback: The component is inserted into the DOM and parameters are available. This is the perfect place for data subscriptions and imperative data loading.
- 3. render: Chooses a template (rarely overridden).
- 4. renderedCallback: The DOM now exists. Warning: It fires after EVERY render, so guard any one-time setup logic.
- 5. disconnectedCallback: Fires on component removal. Always unsubscribe and clean up listeners here.
- 6. errorCallback: Catches errors from the child component tree, allowing you to create error boundary components.
๐งญ 360 Card — Lifecycle Hooks
๐ Gain: Four clear, distinct moments that provide obvious homes for setup and cleanup logic.
๐ฐ Price:
renderedCallback fires after every reactive change. Unguarded work placed here will run repeatedly.๐ Limits:
@api values are not available in the constructor. errorCallback catches errors from descendants, but never the parent's own errors.๐ Scale: On a console tab, a
recordId can change while the component stays mounted. Always use an @wire or an @api setter to handle updates, rather than relying on connectedCallback to refetch.
renderedCallback fires after EVERY render, not just once. Putting unguarded state mutation inside this hook is the most common cause of the dreaded "infinite render loop" error.
๐ Core Q&A & Scenarios
renderedCallback (which fires repeatedly), DOM access in connectedCallback (which is too early), and forgetting to clean up memory in disconnectedCallback.
Detailed breakdown:
- Mistake 1: Reading
@apivalues in the constructor. They are not set yet. Move it toconnectedCallback. - Mistake 2: One-time setup in
renderedCallbackwithout a boolean guard. The setup will run repeatedly and crash third-party libraries. - Mistake 3: Mutating reactive state inside
renderedCallback. This triggers another render, causing an infinite loop. - Mistake 4: Subscribing to Lightning Message Service (LMS) in
connectedCallbackwithout unsubscribing indisconnectedCallback. This causes massive memory leaks and duplicate handlers when switching tabs. - Mistake 5: Expecting a child's DOM to be ready inside the parent's
connectedCallback. The children are not rendered yet.
errorCallback work and what does a production error boundary look like?A: errorCallback(error, stack) placed on a parent catches errors thrown by its descendant components during their lifecycle or rendering. It does NOT catch the parent's own errors, and it does not catch asynchronous or event-handler errors (those need a standard try/catch).
A production error boundary component wraps a feature region. When it catches an error, it flips to a fallback error template, logs the error stack to a server endpoint via a custom logging service, and offers the user a reset button. Without boundaries, one broken child can silently crash an entire page region.
@api recordId which can CHANGE while the component remains mounted (like in Salesforce Console apps). Where does the load logic live?A: Not in connectedCallback. That hook only fires once per mount. If the user clicks a new record in a console app, the recordId changes, but your component will be stuck showing stale data.
Instead, use an @wire that reacts automatically to the '$recordId' parameter, or use an @api setter that fires an imperative fetch when the value actually changes (remember to add an equality check so you don't loop!).
constructor?A: In the constructor, the component is not in the page yet. @api values are undefined, and there is no DOM to manipulate. connectedCallback runs after insertion, making it safe to fetch data. Golden Rule: Talk to absolutely nothing outside the component while inside the constructor.