Not verified hands-on. This article was written from primary documentation rather than reproduced in an org, so it carries no tested-release badge. If you run it and something differs, say so in the comments and it gets tested and corrected.

The short answer: there are five hooks worth knowing. constructor sets field defaults and nothing else, connectedCallback is where subscriptions and initial data fetches belong, renderedCallback is the only place you may touch rendered DOM, disconnectedCallback undoes whatever connectedCallback set up, and errorCallback catches failures thrown by components below you. The one that bites everybody is renderedCallback, because it runs after every render and changing reactive state inside it schedules another render.

The order they run in

A component is constructed, then connected, then rendered. Parents construct before children, but children finish rendering before parents. So a parent’s renderedCallback runs after all of its children have rendered, which is occasionally useful and frequently surprising.

constructor

Call super() first. That is not a style preference. The framework will throw if you do not.

constructor() {
    super();
    this.rows = [];
    this.isLoading = true;
}

The element exists at this point but has no attributes and no children. this.template.querySelector() returns nothing. Public properties set by a parent have not arrived yet, so reading this.recordId here gives you undefined every time. Do not add attributes to the host element and do not call anything asynchronous.

Most components do not need a constructor at all. Field initialisers cover the same ground with less ceremony.

connectedCallback

The element has been inserted into the document. Public properties are set, so this is the earliest point where recordId is reliable. Use it for:

  • Subscribing to a message channel or a platform event
  • Firing an imperative Apex call for data you need immediately
  • Registering an event listener on window or document
  • Starting a timer

It can run more than once on the same instance. If the element is removed from the DOM and reinserted, you get a second connectedCallback, and a subscription created without a matching teardown is now duplicated. Guard anything that must happen once, or make the teardown symmetrical.

You still cannot query the template here. Nothing has rendered.

renderedCallback and the loop

This fires after every single render pass, not once. Any reactive property you change inside it marks the component dirty, the framework renders again, and renderedCallback fires again. That is an infinite loop, and it presents as a frozen tab rather than an error in the console.

renderedCallback() {
    if (this.hasInitialised) {
        return;
    }
    this.hasInitialised = true;
    loadScript(this, CHART_JS).then(() => this.drawChart());
}

The boolean guard is the standard fix, and hasInitialised should be a plain field, not decorated with @track, so that setting it does not itself trigger a render.

Legitimate work in here: initialising a third-party library against a container element, setting focus on first paint, measuring an element’s size. Everything else belongs somewhere earlier. If you find yourself reading values out of the DOM to decide what to render next, the state should have been in JavaScript to begin with.

disconnectedCallback

The mirror of connectedCallback. Unsubscribe from message channels, clear intervals, remove window listeners, disconnect observers. On a record page where users click from one record to the next, a leaked subscription accumulates silently and the symptom appears half an hour later as duplicate handling of a single event.

disconnectedCallback() {
    unsubscribe(this.subscription);
    this.subscription = null;
    window.removeEventListener('resize', this.handleResize);
}

Note that the listener must be the same function reference you added. An inline arrow function passed to addEventListener cannot be removed.

errorCallback

Declare errorCallback(error, stack) and the component becomes a boundary for its descendants. An error thrown in a child’s lifecycle hook or render is caught here instead of breaking the page.

Its scope is narrower than people expect. It does not catch errors in the component’s own hooks, in event handlers, or inside promise chains. A rejected Apex promise will not reach it. Treat it as a safety net around a subtree you do not control, and handle your own failures with try/catch and .catch() where they occur.

The sixth one

render() is not really a lifecycle hook, it is an override that returns a template. Use it to pick between two imported HTML files when the difference is structural rather than conditional. If you are choosing between two markup blocks based on state, lwc:if is clearer and cheaper.

Frequently asked

Why is recordId undefined in my constructor? Public properties are assigned after construction. Read it in connectedCallback, or use a setter if it can change while the component is alive.

Can I call Apex from connectedCallback? Yes, imperatively. A @wire fires on its own schedule and does not need a hook at all.

How do I run code once after the first render only? A boolean field set inside renderedCallback. There is no built-in first-render hook.

Does disconnectedCallback always run? When the component is removed from the DOM, yes. A full page reload or a browser tab closing does not give you one.