Intermediate 30 min read

Project: A Professional Lead Capture Component

By the end of this lesson, you'll be able to:

  • Combine input components, validation, and choice-based fields into one form
  • Handle submission with a loading state and toast feedback
  • Create a new Lead record using Lightning Data Service

Prerequisites: "Submission, Loading States, and Toast Notifications"

What We're Building

A leadCaptureForm component with:

  1. Text, email, and picklist-style fields (Lesson 1) with required validation (Lesson 3).
  2. A lightning-radio-group for Lead Source (Lesson 2).
  3. A submit button with a loading spinner and disabled state while submitting (Lesson 4).
  4. Success and error toast notifications (Lesson 4).
  5. A real createRecord call against the standard Lead object — no Apex needed, per Module 6/7's decision checklist, since this is a straightforward single-record create.

The Component

import { LightningElement } from 'lwc';
import { createRecord } from 'lightning/uiRecordApi';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import LEAD_OBJECT from '@salesforce/schema/Lead';
import FIRST_NAME_FIELD from '@salesforce/schema/Lead.FirstName';
import LAST_NAME_FIELD from '@salesforce/schema/Lead.LastName';
import COMPANY_FIELD from '@salesforce/schema/Lead.Company';
import EMAIL_FIELD from '@salesforce/schema/Lead.Email';
import LEAD_SOURCE_FIELD from '@salesforce/schema/Lead.LeadSource';

export default class LeadCaptureForm extends LightningElement {
    firstName = '';
    lastName = '';
    company = '';
    email = '';
    leadSource = '';
    isSubmitting = false;

    get leadSourceOptions() {
        return [
            { label: 'Web', value: 'Web' },
            { label: 'Referral', value: 'Referral' },
            { label: 'Trade Show', value: 'Trade Show' },
        ];
    }

    handleFirstNameChange(event) { this.firstName = event.target.value; }
    handleLastNameChange(event) { this.lastName = event.target.value; }
    handleCompanyChange(event) { this.company = event.target.value; }
    handleEmailChange(event) { this.email = event.target.value; }
    handleLeadSourceChange(event) { this.leadSource = event.detail.value; }

    isFormValid() {
        return [...this.template.querySelectorAll('lightning-input')]
            .reduce((valid, field) => field.reportValidity() && valid, true);
    }

    async handleSubmit() {
        if (!this.isFormValid()) {
            return;
        }

        this.isSubmitting = true;
        const recordInput = {
            apiName: LEAD_OBJECT.objectApiName,
            fields: {
                [FIRST_NAME_FIELD.fieldApiName]: this.firstName,
                [LAST_NAME_FIELD.fieldApiName]: this.lastName,
                [COMPANY_FIELD.fieldApiName]: this.company,
                [EMAIL_FIELD.fieldApiName]: this.email,
                [LEAD_SOURCE_FIELD.fieldApiName]: this.leadSource,
            },
        };

        try {
            await createRecord(recordInput);
            this.dispatchEvent(new ShowToastEvent({
                title: 'Lead Captured',
                message: `${this.firstName} ${this.lastName} was added successfully.`,
                variant: 'success',
            }));
            this.resetForm();
        } catch (error) {
            this.dispatchEvent(new ShowToastEvent({
                title: 'Could Not Save Lead',
                message: error.body?.message ?? 'An unexpected error occurred.',
                variant: 'error',
            }));
        } finally {
            this.isSubmitting = false;
        }
    }

    resetForm() {
        this.firstName = '';
        this.lastName = '';
        this.company = '';
        this.email = '';
        this.leadSource = '';
    }
}

isFormValid() calls reportValidity() (Lesson 3) on every lightning-input at once using reduce, so a single click surfaces every validation error rather than stopping at the first one.

Why This Matters in Real Projects

This is a realistic, deployable Lead intake form — the exact kind of component a real Salesforce project needs constantly, and it's built entirely from base components, LDS, and the loading/toast pattern, with no Apex whatsoever.

Exercise

Add a Phone field (lightning-input type="tel", not required) to the form, bound to a phone property.

Show hint

Follow the same pattern as the existing fields.

JAVASCRIPT

Exercise

Challenge: explain, as a comment, why isFormValid() uses reduce to check every field rather than returning early on the first invalid one.

Show hint

Think about what reportValidity() does as a side effect on each field.

JAVASCRIPT

Project: A Professional Lead Capture Component Quiz

1. Why does this project use createRecord instead of an Apex controller?

2. What does isFormValid() use to check every field at once?

3. What happens to isSubmitting if createRecord throws an error?

4. What resets the form fields after a successful submission?

5. What determines the picklist-style choices in the Lead Source radio group?

Log in to submit the quiz and save your score.

My Notes

Log in to keep private notes on this lesson.

Questions about this lesson

No questions yet — be the first to ask.

Log in to ask a question about this lesson.

Summary

A hands-on capstone bringing together every lesson in this module into one realistic, production-quality Lead capture form.