Salesforce · · 26 min read

An Introduction to JavaScript for LWC

JavaScript fundamentals for Lightning Web Components — classes, variables, data types, JSON, methods, the this keyword, DOM interaction, collections, operators, conditionals, modules, async programming, promises, destructuring, spread operator, exception handling, events, and debugging.

Part 76: An Introduction to JavaScript for LWC

Welcome back to the Salesforce series. In Parts 72 through 75, we introduced Lightning Web Components, set up our development environment, explored the LWC file structure, and built our first component with HTML templating. All of that work got us comfortable with what LWC looks like on the surface. Now we need to go deeper. Every LWC component is powered by a JavaScript file, and understanding JavaScript is not optional — it is the foundation everything else is built on.

This is the biggest section in the LWC topic, and for good reason. JavaScript controls your component’s behavior, state, data manipulation, event handling, and communication with the Salesforce platform. If you have written Apex before, some of these concepts will feel familiar, but JavaScript has its own patterns and quirks that you need to internalize. We are going to cover everything from basic variables and data types all the way through promises, destructuring, event propagation, and debugging. Treat this post as a reference — bookmark it, come back to it, and use the code examples as starting points for your own experiments.

Let us get into it.


What is JavaScript?

JavaScript is a high-level, interpreted programming language that runs in the browser. It was originally created in 1995 to add interactivity to web pages, and it has since evolved into one of the most widely used languages in the world. When you hear people talk about ES6, ES2015, or ECMAScript, they are referring to versions of the JavaScript specification. LWC is built on modern JavaScript (ES6 and beyond), so everything we cover in this post uses modern syntax.

In the context of LWC, JavaScript is responsible for defining your component’s class, managing its properties, handling user interactions, making server calls, and controlling what gets rendered in the template. The HTML template defines the structure. The JavaScript file defines the logic.

Every LWC component has a JavaScript file that exports a class extending LightningElement. That class is where all the action happens.


How to Create Classes in JavaScript

In modern JavaScript, you create classes using the class keyword. A class is a blueprint for creating objects with shared properties and methods. In LWC, every component is a class.

class Animal {
    constructor(name, sound) {
        this.name = name;
        this.sound = sound;
    }

    speak() {
        console.log(`${this.name} says ${this.sound}`);
    }
}

const dog = new Animal('Rex', 'Woof');
dog.speak(); // Rex says Woof

The constructor method runs automatically when you create a new instance of the class using the new keyword. You can also extend classes using extends, which is exactly what LWC components do.

import { LightningElement } from 'lwc';

export default class MyComponent extends LightningElement {
    greeting = 'Hello World';
}

This is the standard LWC class structure. Your component extends LightningElement, which gives it access to the component lifecycle, rendering engine, and template binding.


How to Create Variables in JavaScript

JavaScript has three ways to declare variables: var, let, and const.

var oldWay = 'avoid this';   // function-scoped, can be redeclared
let modern = 'use this';     // block-scoped, can be reassigned
const fixed = 'and this';    // block-scoped, cannot be reassigned

In LWC and modern JavaScript, you should use let and const exclusively. Use const when the value will not change, and let when it will. Avoid var — it has scoping issues that lead to bugs.

const API_URL = 'https://api.example.com';
let counter = 0;
counter = counter + 1; // this works
// API_URL = 'something else'; // this throws an error

Inside an LWC class, you define properties directly on the class body or in the constructor. These become reactive properties that the template can bind to.

export default class CounterComponent extends LightningElement {
    count = 0; // reactive property

    increment() {
        this.count = this.count + 1;
    }
}

JavaScript Data Types

JavaScript has several built-in data types. Understanding them is essential because LWC templates behave differently depending on the type of data you pass to them.

Primitive types are immutable and include:

let str = 'Hello';          // String
let num = 42;               // Number
let big = 9007199254740991n; // BigInt
let bool = true;            // Boolean
let empty = null;           // Null
let notDefined;             // Undefined
let id = Symbol('unique');  // Symbol

Reference types include objects, arrays, and functions:

let obj = { name: 'Abhishek', role: 'Developer' };
let arr = [1, 2, 3, 4, 5];
let fn = function() { return 'hello'; };

You can check the type of a value using typeof:

typeof 'hello';    // 'string'
typeof 42;         // 'number'
typeof true;       // 'boolean'
typeof undefined;  // 'undefined'
typeof null;       // 'object' (this is a known quirk)
typeof [1, 2];     // 'object'

How to Create Objects Using JSON

JSON stands for JavaScript Object Notation. It is a lightweight data format used to represent structured data. In LWC, you will work with JSON constantly — whether it is data coming back from an Apex method, a wire adapter, or a configuration object you define in your component.

const account = {
    Name: 'Acme Corp',
    Industry: 'Technology',
    AnnualRevenue: 5000000,
    IsActive: true,
    Contacts: [
        { FirstName: 'John', LastName: 'Doe', Email: 'john@acme.com' },
        { FirstName: 'Jane', LastName: 'Smith', Email: 'jane@acme.com' }
    ]
};

You can access properties using dot notation or bracket notation:

console.log(account.Name);            // Acme Corp
console.log(account['Industry']);      // Technology
console.log(account.Contacts[0].Email); // john@acme.com

You can convert between JSON strings and JavaScript objects:

const jsonString = JSON.stringify(account);
const parsed = JSON.parse(jsonString);

This is useful when you need to deep-clone an object in LWC, since Object.assign only does shallow copies:

const clone = JSON.parse(JSON.stringify(account));

How to Create Methods in JavaScript

Methods are functions defined inside a class. In LWC, methods handle user interactions, process data, and call server-side Apex.

export default class Calculator extends LightningElement {
    result = 0;

    add(a, b) {
        return a + b;
    }

    multiply(a, b) {
        return a * b;
    }

    handleCalculate() {
        this.result = this.add(10, 5);
    }
}

You can also define standalone functions outside of classes:

function greet(name) {
    return `Hello, ${name}!`;
}

// Arrow function syntax
const greetArrow = (name) => `Hello, ${name}!`;

Arrow functions are used heavily in LWC, especially in callbacks and array methods. One important difference: arrow functions do not have their own this context — they inherit this from the surrounding scope. This makes them ideal for use inside LWC methods where you need to reference component properties.


How to Call Methods in JavaScript

Calling methods is straightforward. You call class methods using this from within the same class, or on an instance from outside.

export default class MethodDemo extends LightningElement {
    message = '';

    formatMessage(text) {
        return text.toUpperCase();
    }

    handleClick() {
        this.message = this.formatMessage('hello world');
        // this.message is now 'HELLO WORLD'
    }
}

You can also call methods on child components using this.template.querySelector:

handleParentAction() {
    const child = this.template.querySelector('c-child-component');
    if (child) {
        child.somePublicMethod();
    }
}

This pattern is common in LWC for parent-to-child communication.


What is the “this” Keyword in JavaScript

The this keyword refers to the current execution context. In LWC, this almost always refers to the component instance. When you write this.count, you are accessing the count property on the component.

export default class ThisDemo extends LightningElement {
    name = 'Salesforce';

    showName() {
        console.log(this.name); // 'Salesforce'
    }
}

Where this gets tricky is in callbacks. If you use a regular function as a callback, this might not point to the component anymore:

// Problem: 'this' is lost
handleClick() {
    setTimeout(function() {
        console.log(this.name); // undefined -- 'this' is not the component
    }, 1000);
}

// Solution: use an arrow function
handleClick() {
    setTimeout(() => {
        console.log(this.name); // 'Salesforce' -- arrow function preserves 'this'
    }, 1000);
}

This is why arrow functions are so popular in LWC. They keep this pointing to the component instance, even inside nested callbacks.


How to Interact with the DOM in JS

The DOM (Document Object Model) is the browser’s representation of the HTML on the page. In regular JavaScript, you interact with the DOM using methods like document.querySelector. In LWC, you use this.template.querySelector and this.template.querySelectorAll to access elements within your component’s shadow DOM.

// Standard JavaScript DOM access
const heading = document.querySelector('h1');
heading.textContent = 'New Title';
heading.style.color = 'blue';

// LWC DOM access
export default class DomDemo extends LightningElement {
    renderedCallback() {
        const heading = this.template.querySelector('h1');
        if (heading) {
            heading.style.color = 'blue';
        }
    }
}

LWC components use shadow DOM, which means the DOM inside your component is isolated from the rest of the page. You cannot access elements from another component’s shadow DOM, and external code cannot reach into your component. This is a security and encapsulation feature.

You can also use data- attributes to target specific elements:

// In HTML: <div data-id="container"></div>
const container = this.template.querySelector('[data-id="container"]');

How to Create DOM Elements in JS

In standard JavaScript, you can create elements dynamically using document.createElement:

const paragraph = document.createElement('p');
paragraph.textContent = 'This was created dynamically';
paragraph.classList.add('highlight');
document.body.appendChild(paragraph);

In LWC, you generally do not create DOM elements manually. Instead, you use conditional rendering in the template with if:true and if:false directives, or iterate over data with for:each. The framework handles DOM creation for you. However, understanding createElement is valuable because you may encounter it in utility libraries or when working outside of LWC.

// LWC approach: let the template handle DOM creation
// In your JS file
export default class DynamicList extends LightningElement {
    items = ['Item 1', 'Item 2', 'Item 3'];
    showList = true;

    toggleList() {
        this.showList = !this.showList;
    }
}
<!-- In your HTML template -->
<template if:true={showList}>
    <template for:each={items} for:item="item">
        <p key={item}>{item}</p>
    </template>
</template>

What Are Collections?

Collections are data structures that hold multiple values. In JavaScript, the two most common collections are arrays and maps. If you have worked with Lists and Maps in Apex, the concepts translate directly.

  • Arrays are ordered lists of values, accessed by index.
  • Maps are key-value pairs, accessed by key.
  • Sets are collections of unique values with no duplicates.
const myArray = [1, 2, 3];
const myMap = new Map();
const mySet = new Set([1, 2, 2, 3]); // contains 1, 2, 3

Working with Arrays in JS

Arrays are the most commonly used collection in JavaScript and LWC. You will use them constantly to hold lists of records, options, and display data.

const fruits = ['Apple', 'Banana', 'Cherry'];

// Access elements
fruits[0]; // 'Apple'
fruits.length; // 3

// Add elements
fruits.push('Date');        // adds to end
fruits.unshift('Avocado');  // adds to beginning

// Remove elements
fruits.pop();    // removes from end
fruits.shift();  // removes from beginning

// Find elements
fruits.indexOf('Banana');       // 1
fruits.includes('Cherry');      // true
fruits.find(f => f === 'Banana'); // 'Banana'

// Transform arrays
const upper = fruits.map(f => f.toUpperCase());
const filtered = fruits.filter(f => f.startsWith('B'));
const total = [1, 2, 3].reduce((sum, n) => sum + n, 0); // 6

// Slice and splice
const sliced = fruits.slice(0, 2);  // new array, does not modify original
fruits.splice(1, 1);                // removes 1 element at index 1, modifies original

In LWC, map and filter are essential. You will use map to transform Apex data into a format your template can iterate over, and filter to narrow down results.


Working with Maps in JS

Maps store key-value pairs and maintain insertion order. Unlike plain objects, Map keys can be any type — not just strings.

const contactMap = new Map();

// Set values
contactMap.set('001', { name: 'John Doe', email: 'john@example.com' });
contactMap.set('002', { name: 'Jane Smith', email: 'jane@example.com' });

// Get values
contactMap.get('001'); // { name: 'John Doe', email: 'john@example.com' }

// Check existence
contactMap.has('003'); // false

// Size
contactMap.size; // 2

// Delete
contactMap.delete('001');

// Iterate
contactMap.forEach((value, key) => {
    console.log(`${key}: ${value.name}`);
});

How to Loop Through Collections in JS

JavaScript provides several ways to iterate over collections.

const colors = ['Red', 'Green', 'Blue'];

// Standard for loop
for (let i = 0; i < colors.length; i++) {
    console.log(colors[i]);
}

// for...of (iterates over values)
for (const color of colors) {
    console.log(color);
}

// for...in (iterates over keys/indices -- use with objects)
const person = { name: 'Abhishek', city: 'Pune' };
for (const key in person) {
    console.log(`${key}: ${person[key]}`);
}

// forEach
colors.forEach((color, index) => {
    console.log(`${index}: ${color}`);
});

// while loop
let count = 0;
while (count < 3) {
    console.log(count);
    count++;
}

In LWC, forEach, map, and for...of are the most commonly used. Avoid for...in on arrays — it iterates over enumerable properties, which can include things you do not expect.


Using Collections with Switch Statements in JS

Switch statements let you execute different code blocks based on a value. They pair well with collections when you need to process items differently based on their type or category.

const records = [
    { type: 'Account', name: 'Acme' },
    { type: 'Contact', name: 'John Doe' },
    { type: 'Opportunity', name: 'Big Deal' },
    { type: 'Lead', name: 'New Prospect' }
];

records.forEach(record => {
    switch (record.type) {
        case 'Account':
            console.log(`Processing account: ${record.name}`);
            break;
        case 'Contact':
            console.log(`Processing contact: ${record.name}`);
            break;
        case 'Opportunity':
            console.log(`Processing opportunity: ${record.name}`);
            break;
        default:
            console.log(`Unknown type: ${record.type}`);
            break;
    }
});

Always include a default case and break statements. Forgetting break causes fall-through, where multiple cases execute unintentionally.


What Are Operators in JS

Operators perform operations on values. Here are the categories you will use most.

Arithmetic operators:

5 + 3;   // 8 (addition)
10 - 4;  // 6 (subtraction)
3 * 7;   // 21 (multiplication)
20 / 4;  // 5 (division)
10 % 3;  // 1 (modulus/remainder)
2 ** 3;  // 8 (exponentiation)

Comparison operators:

5 === 5;    // true (strict equality -- checks value AND type)
5 == '5';   // true (loose equality -- avoids this)
5 !== 3;    // true (strict inequality)
10 > 5;     // true
3 < 7;      // true
5 >= 5;     // true

Always use === and !== instead of == and !=. Loose equality performs type coercion, which leads to unexpected results.

Logical operators:

true && false;  // false (AND)
true || false;  // true (OR)
!true;          // false (NOT)

Ternary operator:

const status = count > 0 ? 'Active' : 'Inactive';

Nullish coalescing and optional chaining:

const name = account?.Name ?? 'Unknown';
// If account is null/undefined, or account.Name is null/undefined, use 'Unknown'

Optional chaining (?.) and nullish coalescing (??) are extremely useful in LWC when dealing with data that might not be loaded yet.


What Are Conditional Statements in JS

Conditional statements control the flow of your code based on conditions.

const score = 85;

// if / else if / else
if (score >= 90) {
    console.log('Grade: A');
} else if (score >= 80) {
    console.log('Grade: B');
} else if (score >= 70) {
    console.log('Grade: C');
} else {
    console.log('Grade: F');
}

// Ternary for simple conditions
const passed = score >= 60 ? 'Yes' : 'No';

// Short-circuit evaluation
const userName = user && user.name; // if user is truthy, get user.name
const displayName = userName || 'Guest'; // if userName is falsy, use 'Guest'

In LWC, you often use getter methods to compute conditional values that the template binds to:

export default class ConditionalDemo extends LightningElement {
    score = 85;

    get grade() {
        if (this.score >= 90) return 'A';
        if (this.score >= 80) return 'B';
        if (this.score >= 70) return 'C';
        return 'F';
    }

    get isPassing() {
        return this.score >= 60;
    }
}

How to Create Modules in JS

Modules let you split your code into separate files, each with its own scope. A module can export functions, classes, or variables that other modules import. LWC is built entirely on the JavaScript module system.

// mathUtils.js
export function add(a, b) {
    return a + b;
}

export function multiply(a, b) {
    return a * b;
}

export const PI = 3.14159;

// You can also have a default export
export default class MathHelper {
    square(n) {
        return n * n;
    }
}

Each LWC component file uses export default to expose the component class. Utility functions that you share across components should be placed in a shared utility module.


How to Import Modules in JS

Importing is how you bring exported values into another file.

// Import the default export
import MathHelper from './mathUtils';

// Import named exports
import { add, multiply, PI } from './mathUtils';

// Import everything
import * as MathUtils from './mathUtils';
MathUtils.add(2, 3);

// Rename imports
import { add as sum } from './mathUtils';

In LWC, you will see imports from the framework and from Salesforce-specific modules:

import { LightningElement, api, wire, track } from 'lwc';
import getAccounts from '@salesforce/apex/AccountController.getAccounts';
import ACCOUNT_NAME from '@salesforce/schema/Account.Name';
import userId from '@salesforce/user/Id';

Each of these imports brings in specific functionality. The lwc module gives you the base class and decorators. The @salesforce/apex module lets you call Apex methods. The @salesforce/schema module gives you field references. This module system is what makes LWC composable and organized.


Asynchronous vs Synchronous Operations in JS

Synchronous code executes line by line. Each line waits for the previous line to finish before running. Asynchronous code allows certain operations to run in the background while the rest of your code continues executing.

// Synchronous -- each line waits for the previous one
console.log('First');
console.log('Second');
console.log('Third');
// Output: First, Second, Third

// Asynchronous -- setTimeout runs after a delay
console.log('First');
setTimeout(() => {
    console.log('Second (delayed)');
}, 1000);
console.log('Third');
// Output: First, Third, Second (delayed)

In LWC, asynchronous operations are everywhere. Server calls to Apex, wire service data loading, navigation, and toast messages all involve async patterns. Understanding how async works is critical to building components that behave correctly.


How to Execute Async Code in JS

JavaScript provides two main patterns for handling asynchronous code: callbacks and async/await.

Callbacks (the old way):

function fetchData(callback) {
    setTimeout(() => {
        callback('Data loaded');
    }, 1000);
}

fetchData((result) => {
    console.log(result); // 'Data loaded' after 1 second
});

Async/Await (the modern way):

async function loadAccounts() {
    try {
        const result = await getAccounts();
        console.log('Accounts:', result);
    } catch (error) {
        console.error('Error:', error);
    }
}

In LWC, async/await is the preferred approach for imperative Apex calls:

import getAccounts from '@salesforce/apex/AccountController.getAccounts';

export default class AccountList extends LightningElement {
    accounts = [];

    async connectedCallback() {
        try {
            this.accounts = await getAccounts();
        } catch (error) {
            console.error('Failed to load accounts:', error);
        }
    }
}

Promises in JS

A Promise is an object that represents the eventual completion or failure of an asynchronous operation. Every async/await call is working with Promises under the hood.

const myPromise = new Promise((resolve, reject) => {
    const success = true;
    if (success) {
        resolve('Operation succeeded');
    } else {
        reject('Operation failed');
    }
});

// Using .then() and .catch()
myPromise
    .then(result => console.log(result))
    .catch(error => console.error(error));

// Using async/await (equivalent)
async function run() {
    try {
        const result = await myPromise;
        console.log(result);
    } catch (error) {
        console.error(error);
    }
}

You can run multiple promises in parallel using Promise.all:

async function loadAllData() {
    const [accounts, contacts, opportunities] = await Promise.all([
        getAccounts(),
        getContacts(),
        getOpportunities()
    ]);
    console.log(accounts, contacts, opportunities);
}

Promise.all waits for all promises to resolve. If any one of them rejects, the entire call rejects. Use Promise.allSettled if you want to get results from all promises regardless of whether some failed.


Destructuring in JS

Destructuring lets you extract values from objects and arrays into individual variables. It is a clean, concise syntax that you will see everywhere in LWC.

Object destructuring:

const account = { Name: 'Acme', Industry: 'Tech', Rating: 'Hot' };

const { Name, Industry, Rating } = account;
console.log(Name);     // 'Acme'
console.log(Industry); // 'Tech'

// With renaming
const { Name: accountName, Industry: sector } = account;
console.log(accountName); // 'Acme'

// With default values
const { Website = 'N/A' } = account;
console.log(Website); // 'N/A'

Array destructuring:

const colors = ['Red', 'Green', 'Blue'];
const [first, second, third] = colors;
console.log(first);  // 'Red'
console.log(second); // 'Green'

// Skip elements
const [, , last] = colors;
console.log(last); // 'Blue'

In function parameters:

function displayContact({ FirstName, LastName, Email }) {
    console.log(`${FirstName} ${LastName} - ${Email}`);
}

displayContact({ FirstName: 'John', LastName: 'Doe', Email: 'john@example.com' });

In LWC, destructuring is commonly used when handling wire results and event details:

@wire(getAccounts)
wiredAccounts({ error, data }) {
    if (data) {
        this.accounts = data;
    } else if (error) {
        this.error = error;
    }
}

The Spread Operator

The spread operator (...) expands an iterable into individual elements. It is used for copying arrays, merging objects, and passing arguments.

// Copying arrays
const original = [1, 2, 3];
const copy = [...original];

// Merging arrays
const merged = [...original, 4, 5, 6];

// Copying objects
const baseConfig = { theme: 'dark', language: 'en' };
const userConfig = { ...baseConfig, language: 'fr' };
// { theme: 'dark', language: 'fr' }

// Merging objects
const account = { Name: 'Acme', Industry: 'Tech' };
const updated = { ...account, Rating: 'Hot', Industry: 'Finance' };
// { Name: 'Acme', Industry: 'Finance', Rating: 'Hot' }

In LWC, the spread operator is particularly useful when you need to create new arrays or objects to trigger reactivity. LWC tracks changes by reference, so mutating an existing array will not cause a re-render. You need to create a new array:

// This will NOT trigger a re-render
this.items.push('New Item');

// This WILL trigger a re-render
this.items = [...this.items, 'New Item'];

This is one of the most important patterns in LWC development.


Exception Handling in JS

Exception handling lets you catch and respond to errors without crashing your application. In JavaScript, you use try, catch, and finally.

try {
    const result = riskyOperation();
    console.log(result);
} catch (error) {
    console.error('Something went wrong:', error.message);
} finally {
    console.log('This runs regardless of success or failure');
}

You can throw your own errors:

function divide(a, b) {
    if (b === 0) {
        throw new Error('Cannot divide by zero');
    }
    return a / b;
}

try {
    divide(10, 0);
} catch (error) {
    console.error(error.message); // 'Cannot divide by zero'
}

In LWC, exception handling is critical for Apex calls:

async handleSave() {
    try {
        await saveRecord({ fields: this.fields });
        this.dispatchEvent(
            new ShowToastEvent({
                title: 'Success',
                message: 'Record saved',
                variant: 'success'
            })
        );
    } catch (error) {
        this.dispatchEvent(
            new ShowToastEvent({
                title: 'Error',
                message: error.body.message,
                variant: 'error'
            })
        );
    }
}

Always wrap your Apex calls in try/catch blocks. If you do not, a server error will result in an unhandled promise rejection and a poor user experience.


Event Types and Event Handlers

Events are how components communicate in JavaScript and in LWC. An event is an object that represents something that happened — a click, a key press, a data load, or a custom action.

Standard DOM events:

// In LWC HTML template
// <button onclick={handleClick}>Click Me</button>
// <input onchange={handleChange} />
// <div onmouseover={handleHover}></div>

export default class EventDemo extends LightningElement {
    handleClick(event) {
        console.log('Button clicked');
        console.log('Event type:', event.type); // 'click'
        console.log('Target:', event.target);   // the button element
    }

    handleChange(event) {
        const inputValue = event.target.value;
        console.log('Input value:', inputValue);
    }
}

Custom events in LWC are used for child-to-parent communication:

// Child component dispatches a custom event
this.dispatchEvent(new CustomEvent('select', {
    detail: { recordId: '001xx000003DGbX' }
}));

// Parent component handles it in the template
// <c-child onselect={handleSelect}></c-child>

handleSelect(event) {
    const selectedId = event.detail.recordId;
    console.log('Selected record:', selectedId);
}

Event Propagation

Event propagation describes how events travel through the DOM. There are three phases: capturing, target, and bubbling.

  • Capturing phase — The event travels down from the document root to the target element.
  • Target phase — The event reaches the element that triggered it.
  • Bubbling phase — The event travels back up from the target to the document root.

By default, events bubble up. You can control this when creating custom events:

// Event that bubbles (parent can catch it)
this.dispatchEvent(new CustomEvent('notify', {
    bubbles: true,
    composed: true, // crosses shadow DOM boundaries
    detail: { message: 'Hello from child' }
}));

// Event that does NOT bubble (only direct parent catches it)
this.dispatchEvent(new CustomEvent('update', {
    detail: { value: 42 }
}));

In LWC, composed: true is important. Without it, the event stops at the shadow DOM boundary and cannot be caught by grandparent components. Use bubbles: true and composed: true when you need an event to travel beyond the immediate parent.

You can stop propagation if you want to prevent an event from bubbling further:

handleClick(event) {
    event.stopPropagation(); // prevents the event from bubbling up
}

How to Debug JS in the Browser Console

Debugging is how you figure out what your code is actually doing. The browser console is your primary tool.

Console methods:

console.log('Basic output');
console.error('Error message');       // red text in console
console.warn('Warning message');      // yellow text in console
console.table([{ a: 1, b: 2 }, { a: 3, b: 4 }]); // formatted table
console.group('Group Label');
console.log('Inside group');
console.groupEnd();
console.time('timer');
// ... some operation
console.timeEnd('timer'); // logs elapsed time

Using the debugger statement:

handleClick() {
    const value = this.computeValue();
    debugger; // execution pauses here when DevTools is open
    this.result = value;
}

When you open Chrome DevTools (F12 or Cmd+Option+I on Mac) and execution hits a debugger statement, the browser pauses and you can inspect variables, step through code line by line, and examine the call stack.

Tips for debugging LWC:

  1. Open Chrome DevTools and go to the Sources tab. Your LWC files are under the modules folder.
  2. Set breakpoints by clicking on line numbers in the Sources tab.
  3. Use console.log liberally during development, but remove them before deploying.
  4. Check the Network tab to see Apex call requests and responses.
  5. Use the LWC Inspector browser extension for a component tree view.

Section Notes

This was a big one. JavaScript is the engine behind every LWC component, and the topics we covered here will come up in every single section that follows. Here are the key takeaways:

  • Use const and let, never var. Use const by default and let only when you need to reassign.
  • Always use strict equality (===) over loose equality (==).
  • Arrow functions preserve this, which is critical inside LWC methods with callbacks.
  • The spread operator (...) is essential for triggering LWC reactivity. Create new arrays and objects instead of mutating existing ones.
  • Destructuring keeps your code clean, especially when handling wire results and event details.
  • Always wrap Apex calls in try/catch blocks with async/await.
  • Custom events with bubbles and composed are how you communicate across component boundaries.
  • Use console.log, debugger, and Chrome DevTools to inspect your component’s behavior at runtime.

Do not try to memorize all of this at once. Use this post as a reference. When you are building a component and you need to remember how destructuring works, or how to create a custom event, come back here.


PROJECT: Create an LWC That Loops Through Data in a JSON Object and Outputs It in the Browser Console

Let us put several of these concepts together. We are going to create an LWC component that defines a JSON data structure, loops through it, and outputs formatted results to the browser console. This exercises classes, properties, JSON, arrays, loops, template literals, and console debugging.

jsonLogger.js:

import { LightningElement } from 'lwc';

export default class JsonLogger extends LightningElement {
    teamData = {
        teamName: 'Cloud Solutions',
        department: 'Engineering',
        members: [
            { id: 1, name: 'Abhishek Katyare', role: 'Lead Developer', skills: ['Apex', 'LWC', 'Flows'] },
            { id: 2, name: 'Sarah Chen', role: 'Senior Developer', skills: ['Apex', 'Integration', 'Testing'] },
            { id: 3, name: 'Marcus Johnson', role: 'Admin', skills: ['Flows', 'Reports', 'Security'] },
            { id: 4, name: 'Priya Patel', role: 'Junior Developer', skills: ['LWC', 'HTML', 'CSS'] }
        ]
    };

    displayData = [];

    connectedCallback() {
        this.processTeamData();
    }

    processTeamData() {
        const { teamName, department, members } = this.teamData;

        console.group(`Team: ${teamName} | Department: ${department}`);
        console.log(`Total members: ${members.length}`);

        const processed = members.map(member => {
            const { id, name, role, skills } = member;
            const skillList = skills.join(', ');

            console.log(`[${id}] ${name} - ${role} | Skills: ${skillList}`);

            return {
                ...member,
                skillCount: skills.length,
                summary: `${name} (${role}) - ${skills.length} skills`
            };
        });

        console.groupEnd();

        // Filter developers only
        const developers = processed.filter(m =>
            m.role.toLowerCase().includes('developer')
        );
        console.log('Developers on the team:', developers.length);

        // Find the member with the most skills
        const mostSkilled = processed.reduce((max, member) =>
            member.skillCount > max.skillCount ? member : max
        );
        console.log('Most skilled member:', mostSkilled.name, `(${mostSkilled.skillCount} skills)`);

        // Create a skill frequency map
        const skillMap = new Map();
        members.forEach(member => {
            member.skills.forEach(skill => {
                const count = skillMap.get(skill) || 0;
                skillMap.set(skill, count + 1);
            });
        });

        console.group('Skill Frequency');
        skillMap.forEach((count, skill) => {
            console.log(`${skill}: ${count} member(s)`);
        });
        console.groupEnd();

        this.displayData = processed;
    }
}

jsonLogger.html:

<template>
    <lightning-card title="Team Data Logger" icon-name="standard:people">
        <div class="slds-m-around_medium">
            <p class="slds-m-bottom_small">
                Open the browser console (F12) to see the logged output.
            </p>
            <template for:each={displayData} for:item="member">
                <div key={member.id} class="slds-box slds-m-bottom_x-small">
                    <p><strong>{member.name}</strong> - {member.role}</p>
                    <p class="slds-text-body_small">{member.summary}</p>
                </div>
            </template>
        </div>
    </lightning-card>
</template>

jsonLogger.js-meta.xml:

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>62.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__AppPage</target>
        <target>lightning__HomePage</target>
    </targets>
</LightningComponentBundle>

Deploy this component, drop it onto a Lightning page, and open the browser console. You should see the team data printed in organized groups with member details, developer counts, the most skilled team member, and a skill frequency breakdown. This project combines JSON objects, destructuring, spread operator, array methods (map, filter, reduce, forEach), Maps, template literals, and console output — all in one component.

In the next section, we will start using these JavaScript skills to build reactive LWC components with decorators, property binding, and the wire service. See you there.