Handling Errors in LWC

Handling Errors in LWC

Understand the key techniques for handling errors and exceptions in LWC — and know exactly when and how to apply each approach.

1. Introduction

Error handling is essential in Lightning Web Components (LWC) because it bridges the gap between failures on the server (Apex, permissions, data issues) and a graceful experience for the user on the client side.

In LWC, errors can originate from multiple sources:
Apex method failures — data issues, governor limits, incorrect permissions.
JavaScript runtime errors — unexpected null values, bad logic.
UI validation — invalid form input before submission.

LWC gives you several tools to handle all of these:
@wire error handling — for declarative Apex calls.
.then() / .catch() — for imperative Apex calls.
try-catch blocks — for async/await patterns.
Client-side validation — for UI form checks.

2. Handling Errors in @wire (Declarative) Apex Calls

When using @wire to call Apex, errors are returned via the { data, error } destructured object. The Apex method should throw an AuraHandledException so the error propagates cleanly to the client.

Apex (Server Side):

public with sharing class MyApexController {
    @AuraEnabled(cacheable=true)
    public static List<Account> getAccounts() {
        try {
            return [SELECT Id, Name FROM Account LIMIT 10];
        } catch (Exception e) {
            throw new AuraHandledException(
                'Error retrieving accounts: ' + e.getMessage()
            );
        }
    }
}

LWC JavaScript:

import { LightningElement, wire } from 'lwc';
import getAccounts from '@salesforce/apex/MyApexController.getAccounts';

export default class MyComponent extends LightningElement {
    accounts;
    error;

    @wire(getAccounts)
    wiredAccounts({ data, error }) {
        if (data) {
            this.accounts = data;
            this.error = undefined;
        } else if (error) {
            this.error = error.body.message; // Capture error message
            this.accounts = undefined;
        }
    }
}
<template>
    <template if:true={accounts}>
        <ul>
            <template for:each={accounts} for:item="account">
                <li key={account.Id}>{account.Name}</li>
            </template>
        </ul>
    </template>
    <template if:true={error}>
        <div class="slds-text-color_error">{error}</div>
    </template>
</template>

• The @wire function receives both data and error — always handle both states.
• Access the message via error.body.message for Apex-thrown exceptions.
• Always set the opposite property to undefined to avoid stale state in the template.

3. Handling Errors in Imperative Apex Calls

When invoking Apex imperatively (e.g., on a button click or form submit), use .then() and .catch() on the returned Promise to handle success and failure states.

Apex (Server Side — DML, no cacheable):

public with sharing class MyApexController {
    @AuraEnabled
    public static String saveAccount(String accountName) {
        try {
            Account acc = new Account(Name = accountName);
            insert acc;
            return acc.Id;
        } catch (Exception e) {
            throw new AuraHandledException(
                'Error saving account: ' + e.getMessage()
            );
        }
    }
}

LWC JavaScript:

import { LightningElement } from 'lwc';
import saveAccount from '@salesforce/apex/MyApexController.saveAccount';

export default class MyComponent extends LightningElement {
    accountName = '';
    error;
    successMessage;

    handleAccountNameChange(event) {
        this.accountName = event.target.value;
    }

    saveAccountHandler() {
        saveAccount({ accountName: this.accountName })
            .then(result => {
                this.successMessage = 'Account created successfully: ' + result;
                this.error = undefined;
            })
            .catch(error => {
                this.error = error.body.message;
                this.successMessage = undefined;
            });
    }
}
<template>
    <lightning-card title="Create Account">
        <div class="slds-p-around_medium">
            <lightning-input label="Account Name" value={accountName}
                onchange={handleAccountNameChange}>
            </lightning-input>
            <lightning-button label="Save Account"
                onclick={saveAccountHandler}>
            </lightning-button>
            <template if:true={error}>
                <div class="slds-text-color_error">{error}</div>
            </template>
            <template if:true={successMessage}>
                <div class="slds-text-color_success">{successMessage}</div>
            </template>
        </div>
    </lightning-card>
</template>

• If you try to save without a name, Apex will throw an error that surfaces in the UI.
• The .catch() block captures the error and sets it to error.body.message.
• Always clear the opposite state (success/error) to keep the UI consistent.

4. Handling UI / Form Validation Errors

You can also handle form validation errors on the client side in LWC to ensure data integrity before submitting data to Apex. This reduces unnecessary server calls and gives instant feedback.

import { LightningElement } from 'lwc';

export default class FormValidationComponent extends LightningElement {
    name = '';
    email = '';
    errorMessages = [];

    handleNameChange(event) { this.name = event.target.value; }
    handleEmailChange(event) { this.email = event.target.value; }

    validateForm() {
        this.errorMessages = [];
        if (!this.name) {
            this.errorMessages.push('Name is required.');
        }
        if (!this.email) {
            this.errorMessages.push('Email is required.');
        } else if (!/^[^@]+@[^@]+\.[^@]+$/.test(this.email)) {
            this.errorMessages.push('Email format is invalid.');
        }
        return this.errorMessages.length === 0;
    }

    handleSubmit() {
        if (this.validateForm()) {
            // Proceed with Apex call or form submission
            console.log('Form submitted');
        } else {
            // Show validation errors
            console.log('Form has errors');
        }
    }
}
<template>
    <lightning-card title="Validation Error">
        <div class="slds-p-around_medium">
            <lightning-input label="Name" value={name}
                onchange={handleNameChange}></lightning-input>
            <lightning-input label="Email" value={email}
                onchange={handleEmailChange}></lightning-input>
            <lightning-button label="Submit"
                onclick={handleSubmit}></lightning-button>
            <template if:true={errorMessages.length}>
                <ul class="slds-text-color_error">
                    <template for:each={errorMessages} for:item="message">
                        <li key={message}>{message}</li>
                    </template>
                </ul>
            </template>
        </div>
    </lightning-card>
</template>

• If Name and Email are left empty, errors will be thrown and displayed instantly — no server call needed.
• The validateForm() method collects all errors into an array and returns false if any exist.
• Only call Apex inside handleSubmit() when validateForm() returns true.

5. Key Differences

Aspect @wire (Declarative) Imperative (.then/.catch) UI Validation
Trigger Auto on component load Manual (button click, event) Before form submit
Error Access { data, error } → error.body.message .catch(error => error.body.message) Custom JS array / logic
Supports DML ❌ Read-only only ✅ Yes (insert, update, delete) N/A (client-side only)
Use Case Load data on render User-triggered actions Form data integrity

6. When to Use Which?

Use @wire error handling when:
• You are fetching read-only data automatically when the component loads.
• The Apex method is annotated with cacheable=true.
• You want automatic re-fetch when reactive properties (like recordId) change.

Use Imperative .then()/.catch() when:
• The Apex call is triggered by a user action (button click, form submit).
• You need to perform DML operations (insert, update, delete) — @wire does not support DML.
• You need to run complex logic before or after the Apex call.
• You need fresh data every time without relying on cache.

Use UI Validation when:
• You need to validate form fields before making any server call.
• You want to give users instant feedback without a round-trip to Apex.
• You need custom validation rules beyond what lightning-input provides natively.

7. Quick Reference

@wire error pattern:

@wire(apexMethodName, { param1: '$reactiveParam' })
wiredHandler({ data, error }) {
    if (data) { /* handle data */ }
    else if (error) { this.error = error.body.message; }
}

Imperative error pattern:

apexMethodName({ param1: value1 })
    .then(result => { /* handle success */ })
    .catch(error => { this.error = error.body.message; });

async/await error pattern:

async handleAction() {
    try {
        const result = await apexMethodName({ param1: value1 });
        // handle success
    } catch (error) {
        this.error = error.body.message;
    }
}

📝 Remember: Use cacheable=true on your Apex annotation when using @wire. For imperative calls that perform DML, do NOT use cacheable=true — it will throw a runtime error. Always access the error message via error.body.message for AuraHandledExceptions.

Popular posts from this blog

Hello World using LWC

Lightning Card in LWC