SFDC Stop - Always the latest about Salesforce


Full Tutorial Series with videos, free apps, live sessions, salesforce consulting and much more.


Telegram logo   Join our Telegram Channel
Showing posts with label LWC. Show all posts
Showing posts with label LWC. Show all posts

Monday, 15 January 2024

Embed screen flow in LWC component: Pass data to screen flow and Receive data from Screen Flow

Hello Trailblazers,


You might have come across the below blog posts published by me in the past:

  1. How to pass data from lwc to screen flow in salesforce?
  2. How to pass data from screen flow to lwc in salesforce?


You might be thinking, what the heck are we doing in this post then???


Give me a moment to clarify: In the above posts, we actually embedded a LWC component within a screen flow and passed data to it/received data from it. However, in today's post, we're going to do exactly the opposite. We're going to embed a screen flow within a LWC component and pass data to screen flow, receive data from screen flow. We're going to use lightning-flow lwc component provided by salesforce in this tutorial. So, without spending much time on discussion. Let's begin!


First of all we're going to create a very basic LWC component called flowContainer which will have our flow. We're going to embed this component on the homepage just like other demo components which we created. The content of .meta-xml file for the same is provided below:

flowContainer.js-meta.xml

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

Screen Flow - Duplicate Contacts

Before jumping on to more LWC code, let's create our screen flow first. This flow, named as Duplicate Contacts, is going to do the following:

  1. Get the list of contact ids from flowContainer LWC
  2. Create a new account record
  3. Query the existing contact records using their ids, create a copy of those, tag them to the new account record and insert them in salesforce
  4. Pass the new account record id to flowContainer LWC

Follow the below steps to create our screen flow:

1. Go to setup. Search for flow and click the New Flow button

2. In the new flow screen, choose Screen Flow option and click Create

3. In the Toolbox on the left hand side, click New Resource. The inputs are provided below:
      Resource Type: Variable
      API Name: ids
      Description: List of contact ids
      Data Type: Text
      Availability Outside the Flow: Available for input

We basically created a collection variable ids here, which will receive the list of contact ids from our flowContainer LWC component. We checked Available for input as we're going to receive it's value from outside the flow i.e. from our LWC component.

Note: No need to check these boxes otherwise. I've seen a lot of developers checking both Available for input and Available for output boxes without any reason. Please see if you really need an input in this variable from somewhere outside the flow and then only check this checkbox.

Before moving ahead, let's save the flow using the Save button on the top right. Click the Save button, fill in the details as shown below and click the save button present on the popup again to save the flow.
      Flow Label: Duplicate Contacts
      Flow API Name: DuplicateContacts
      Description: This flow will duplicate existing contact records based on ids and link them with a new account record

Inside this flow, we want to have a screen first which will create a new Account record. But before that, we need a resource of type Variable and object as Account. The details of the new resource is provided below:
      Resource Type: Variable
      API Name: NewAccount
      Description: This variable will store the new account record which is created
      Data Type: Record
      Object: Account

The screen to create a new account is provided below:

The header is: Enter Account Details
We added a single textbox in this screen which will store the Account Name as provided below:

Note that we switched to the Fields tab, populated the RecordVariable with our NewAccount variable that we created before and then we dragged + dropped the Account Name field to the screen. This will automatically bind the value entered by the user to the Name field of our NewAccount variable.

Now, we can add the Create Records element to our flow in order to insert this new account record. The details of Create Records element are provided below:
      Label: Create Account
      API Name: Create_Account
      Description: Insert the account record present in NewAccount variable in salesforce
      How Many Records to Create: One
      How to Set the Record Fields: Use all values from a record
      Record: NewAccount
Once our account record is created, we need to duplicate contact records and attach them to this new account record. In order to do it, we're going to use our Get Records element to query the contact records using ids. The details are provided below:
      Label: Query Contacts
      API Name: Query_Contacts
      Description: Query contact records based on record ids passed to the flow
      Object: Contact
      Filter: Id In {!ids}
      Sort Order: Not Sorted
      How Many Records to Store: All records
      How to Store Record Data: Automatically store all fields
If you notice above, we're using the {!ids} variable here which will have the ids of our existing contact records to query them. Now, we're going to remove the Id from these contact records to create new records, tag them to the newly created account and store them in a list.

In order to do that, we need a Loop element using which we'll loop all the queried contacts. The details of the same are provided below:
      Label: Iterate Contacts
      API Name: Iterate_Contacts
      Description: Iterate the queried contacts
      Collection Variable: {!Query_Contacts}
      Direction: First item to last item

Let's create a new list first to store our modified contact records which we're going to insert. The details are provided below:
      Resource Type: Variable
      API Name: contactsList
      Description: List of contact records
      Data Type: Record
      Object: Contact
      Allow multiple values (collection): True

Now, inside this loop, we'll set the Id and AccountId for every contact using an assignment element and add that contact record to a new list of contacts: contactsList which we created before.

The details for the same are as follows:
      Label: Set Id and AccountId
      API Name: Set_Id_and_AccountId
      Description: Set Id as empty and AccountId using the Id of newly created account for the current contact record
      Variable values:
      {!Iterate_Contacts.Id} <Equals> <Blank>
      {!Iterate_Contacts.AccountId} <Equals> {!NewAccount.Id}
      {!contactsList} <Add> {!Iterate_Contacts}

After this screen we'll use the Create Records element to insert contactsList in salesforce. The details for the same are provided below:
      Label: Create Contacts
      API Name: Create_Contacts
      Description: Insert the list of newly created contact records in salesforce
      How Many Records to Create: Multiple
      Record Collection: contactsList


One thing that we should update here is our NewAccount variable. We want to redirect the user to the newly created account record from our flowContainer LWC component. That means, we need to pass the new account from flow to LWC. Therefore, we can set Availability Outside the Flow as Available for output as shown below:

Now, our flow is complete. Make sure to Activate the flow. It's time to move on to the html code for our LWC component.

flowContainer.html

This component will basically show a button, which will launch our screen flow. Let's see the code:
<template>
    <template lwc:if={showFlow}>
        <lightning-flow
            flow-api-name="DuplicateContacts"
            flow-input-variables={inputVariables}
            onstatuschange={handleStatusChange}>
        </lightning-flow>
    </template>
    <template lwc:else>
        <lightning-button label="Duplicate Contacts" onclick={launchFlow}></lightning-button>
    </template>
</template>
As you can see above, we have two templates rendered on the basis of a boolean showFlow which is used in lwc:if attribute added in a template tag. If showFlow is true, we're displaying the DuplicateContacts screen flow in our lwc component using lightning-flow tag. If you notice inside lightning-flow tag, we've specified value for 3 attributes:

  1. flow-api-name: This should be the API name of the flow. For our flow, it's DuplicateContacts.
  2. flow-input-variables: This refers to the array of input variables i.e. the flow variables whose values we're going to pass from this LWC component.
  3. onstatuschange: We're capturing the statuschange event here and calling our handleStatusChange method. statuschange event is fired whenever the status of the flow is changed. For example: when the flow is started/paused/finished etc.

We're going to define showFlow, inputVariables - variables and handleStatusChange method in our js file. In the lwc:else section, we have a lightning-button whose label is Duplicate Contacts and on clicking of that button we're calling our launchFlow method which we're going to define in our js file which will launch our flow. So, let's move on to our js file to complete this component.

flowContainer.js

This is the most important part of the tutorial as this is where we're going to pass data from LWC to screen flow and we're going to receive data from screen flow in our LWC component. Let's take a look at the code below:
import { LightningElement } from 'lwc';
import { NavigationMixin } from 'lightning/navigation';

export default class FlowContainer extends NavigationMixin(LightningElement) {

    // * Boolean to display/hide flow
    showFlow = false;

    // * Ids of contact records to be passed to flow
    contactIds = [
        '003H3000001l11BIAQ',
        '003H3000001l11AIAQ',
        '003H3000001l112IAA'
    ];

    // * Input variables to pass to flow from LWC
    inputVariables = [
        {
            name: 'ids',
            type: 'String',
            value: this.contactIds
        }
    ]

    /**
     * @description This method is used to launch the flow from lwc
     */
    launchFlow() {
        this.showFlow = true;
    }

    /**
     *
     * @param {object} event status change event - received when flow state is changed
     * @description This method will be called whenever the state of the flow is updated
     */
    handleStatusChange(event) {
        if(event.detail.status === 'FINISHED') {
            let accountVariable = event.detail.outputVariables?.find(
                outputVariable => outputVariable.name === 'NewAccount'
            );
            this.navigateToRecordPage(accountVariable.value.Id);
        }
    }

    /**
     *
     * @param {string} recordId Id of the record
     * @description This method is used to navigate the current user to the detail page of the record whose id is passed as parameter
     */
    navigateToRecordPage(recordId) {
        this[NavigationMixin.Navigate]({
            type: 'standard__recordPage',
            attributes: {
                recordId: recordId,
                actionName: 'view',
            },
        });
    }
}
Let's understand the above code line by line. First of all we imported NavigationMixin from lightning/navigation library. We're going to navigate to the record page of our newly created account record using this method. We also updated our FlowContainer class to extend NavigationMixin(LightningElement) for the same purpose.

We introduced three variables in our js file:

The first one is showFlow, which is a boolean variable and is false by default. This variable will be toggled to true when we want to display the flow in our LWC component. Remember the lwc:if condition in our HTML which is using this variable? If you check the HTML again you'll notice that by default our lightning-button will be visible through the lwc:else condition as showFlow is false.

The second one is contactIds, which is a string array that consist of the ids of contact records. We're going to pass this array to our flow ids variable.

The third one is inputVariables array, which is passed to our lightning-flow's flow-input-variables attribute. This is basically an array of objects, where each object is having a name, a type and a value. For each variable that we want to populate in our flow, through our LWC, we should have an entry for that in this array.

In each object inside our inputVariables array:
  • The name should be the name of the variable as defined in our flow. For our entry it's ids as we defined ids variable in the flow which will store the contact ids.
  • The type is the data type of the variable. In our case it's String (Text in flow)
  • The value should be the value of the variable defined in our flow. For our scenario, we want to pass the list of contact ids as value to our ids variable. Therefore, we've referred to our contactIds variable here which is defined above in the js and passed that as the value for our ids flow variable.

Note: I'm re-iterating the same thing so that you don't miss this. If you notice, this is the point where we've defined that: in the ids variable of our screen flow, we want to pass the array of contact ids we've hardcoded in our js. This value can come from different sources depending upon your use case. For example: You can have a lightning-datatable where you can select some records and as the records are selected, you can populate the contactIds array. This array will automatically be passed to the ids variable in the flow as the flow is launched. 

After that we've defined 3 methods which are as follows:

  1. launchFlow(): This method will be called when we click Duplicate Contacts button in our HTML. It'll set showFlow attribute to true which will automatically hide the button and will show the screen flow in our LWC. In a way we can say, this method is used to launch our screen flow.

  2. handleStatusChange(event): This method will be called whenever the flow status is updated. It's binded to statuschange event of our lightning-flow component. Inside this method, we need to check: If the flow is finished, we need to redirect the user to the detail page of newly created account record. We're receiving event inside this method as a parameter so, we're basically checking here if: event.detail.status === FINISHED i.e. if the flow is finished, we're referring to the outputVariables in our flow using event.detail.outputVariables to get the newly created account record.

    Remember that Available for output checkbox which we checked in our NewAccount variable inside the flow? That was marked so that it's value can be accessed outside the flow. Therefore, that variable will be received in the array of our ouputVariables accessed through event.detail.outputVariables.

    I'm sharing the NewAccount flow variable below again for your reference:
    As you can notice, we marked this as Available for output so that we can receive the value of this variable outside the flow i.e. in our case - inside our flowContainer lwc component. If you log event.detail.outputVariables, you'll get an array as shown below:
    As you can see above, there is only a single entry in this array and that is for our NewAccount variable. It's name property is having a value as NewAccount, it's objectType is Account. This variable is not a collection so isCollection is false. It's coming from flow DuplicateContacts so the flow name is the same and it's dataType is SOBJECT as it stores the value of an sObject record. Moving onto the value property, as we only populated the name of the account while creating the record from flow, it's value is an object having only two properties: Id and Name.

    Moving back to our code, we are using find() method to find the outputVariable from our outputVariables array where name = NewAccount. This will return us the first entry of our array. We're storing this in a js variable named accountVariable. Finally, we're accessing this account record's id using accountVariable.value.Id and passing it to our navigateToRecordPage() method which will navigate the current user to the new account's record page.

    Note: This is the point where we're receiving data from flow in our LWC component. The variables present in our flow which are marked as available for output, will be received in our LWC component inside outputVariables array under our event.

  3. navigateToRecordPage(recordId): This method is used to navigate the current user to the detail page of the record whose record id is passed as a parameter. This method is called from handleStatusChange() method. We're using NavigationMixin.Navigate to navigate to the standard record page and we're passing recordId as the value to our recordId attribute.

That's all for our js part as well! Let's embed our component inside the homepage and see how it works. Take a look at the demo below:
As you can see above, as we clicked on Duplicate Contacts button in our LWC, the screen flow launched. We entered the account name in the screen flow which created a new account named My Account and all the 3 contacts (whose ids we hardcoded in our js) are duplicated and attached to this new account record. We finally received this new account record in our js as the flow finished and navigated to the account detail page using our LWC.

If you see below, we have all the 3 newly created contacts linked to the same My Account record, however the old contacts are present as is and linked to their own account records.

That's all for this tutorial, I hope you liked it. Let me know your feedback in the comments down below.

Happy Trailblazing!!

Sunday, 7 January 2024

Log LWC Event Messages using Lightning Logger : Event Monitoring in Salesforce

Hello Trailblazers,

In this post we're going to talk about lightning/logger module. This module can be used to log messages to salesforce event monitoring from your lightning web components. You can log the error messages as well as any kind of interactions that a user is having with your LWC component. Let's see how!

A simple LWC component logging a message using Salesforce Event Monitoring on button click

Before jumping onto the code, I just want to share a one liner about Salesforce Event Monitoring. Event Monitoring is basically a tool (EventLogFile object) in salesforce that you can use to monitor events in your org and keep your data secure. It can track different types of events like: login, logout, web clicks, apex executions, report exports etc. You can also enable event monitoring for your custom LWC components and can use the library to log events in this EventLogFile object. You can learn more about event monitoring in this trailhead module.

Turn on Event Monitoring for LWC

In order to turn on event monitoring for lightning web components, we can go to Setup->Event Monitoring Settings and turn on the switch for Enable Lightning Logger Events as shown below:
Make sure Generate event log files switch is turned on as well.

Note: As per salesforce documentationThis change is available to customers who purchased Salesforce Shield or Salesforce Event Monitoring add-on subscriptions.

Let's begin by creating our LWC component:

eventLogDemo.html

I created a new LWC component named eventLogDemo. The HTML code for the same is provided below:
<template>
    <lightning-button label="Click Me!" onclick={createEventLog}></lightning-button>
</template>
As you can see above, I defined a simple button with label Click Me! and on clicking of this button, I'm calling my createEventLog() method which we're going to define in our js

eventLogDemo.js

import { LightningElement } from 'lwc';
import { log } from 'lightning/logger';

export default class EventLogDemo extends LightningElement {

    createEventLog() {
        log('Click Me button clicked!');
        console.log('Event Log created!');
    }
}
For the js part, first of all, I imported log method from lightning/logger library. Inside our EventLogDemo class, I defined createEventLog() method which we're calling on clicking the button. This method is calling the log method we imported from the module and is passing a string message in the parameter i.e. Click Me button clicked!. After that, we're having a console.log statement as Event Log created! which is the message printed on the console.

eventLogDemo.js-meta.xml

We also did common changes in our meta.xml file to embed this component in our homepage as shown below:

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

Now, it's time to do see this in action. Let's begin!

Demo

I enabled Debug Mode for my user, so that I can see additional console.logs as well along with the one I have in my component. You can do the same by navigating to Setup -> Debug Mode and enabling it for your user as shown below:
I embedded my component on the homepage and as I clicked on the Click Me! button, I received an output as shown below:
If you notice above, as I called log() method from the logger module, a message from client.js is printed on the console (because I have lightning components debug mode enabled). This object has the message as Click Me button clicked! which is the same that I passed in log() method. After this, we have our console.log() message printed as well i.e. Event Log created!.

Now, In order to view our event logs, we can query them using the below query:
SELECT Id, EventType, CreatedDate, LogFileLength, LogDate, ApiVersion, LogFileContentType, Sequence, Interval, LogFile FROM EventLogFile WHERE Interval = 'Hourly' AND EventType = 'LightningLogger'

I've mentioned Interval = 'Hourly' AND EventType = 'LightningLogger' to get only those event logs which are created using our LWC component. The result is provided below:

Note: I'm using a scratch org and it took somewhere about 2 hours for logs to start appearing after I generated them by clicking the button (the interval is Hourly for these). It might not be the same case for a production org (maybe you can check this and let me know in the comments down below). However, in the Event Monitoring Trailhead, it's written that An event log file is generated when an event occurs in your organization and is available to view and download after 24 hours. So you might have to wait more before you can access the event log files. It's also written that - all log files have 1 day data retention, you can increase it to 30 days for enterprise, unlimited and performance edition at an extra cost.

If you want to download this log file, you can do that by calling the API as shown in the LogFile column as: /services/data/v59.0/sobjects/EventLogFile/0AT1y0000053cjXGAQ/LogFile. You can also download the same using Salesforce Event Log File Browser tool.

Note: Salesforce Event Log File Browser is not an official salesforce tool.

Salesforce Event Log File Browser

To use this tool, go to: https://salesforce-elf.herokuapp.com
You can click on Production Login if you're using a developer/production org or Sandbox Login if you're using a sandbox/scratch org. You'll get the OAuth screen as shown below:
Click on Allow

You'll land to the below page where you can see all the event logs:
You can filter the results using the dropdowns/picklists present above. As you can see below, I selected the event type as LightningLogger and got the below output:
In the Action column, I have two buttons. I can download the CSV log file or a shell script - which will download the log file to my system. I downloaded the CSV log file and the output for the same is shown below:
If you notice above, the PAGE_URL is /lightning/page/home which specifies that this log is triggered from our homepage as the lwc component is embedded in the homepage and the MESSAGE is: Click Me button clicked! which is the same as we passed to the log() method. The 3 entries here means that I clicked this button (or fired this event) 3 times during the hour for which this log file is generated. We can also pass a js object to our log method which is automatically stringified. The maximum string length is 4096 characters. I modified our js code a little bit to pass an object as shown below:
import { LightningElement } from 'lwc';
import { log } from 'lightning/logger';

export default class EventLogDemo extends LightningElement {

    msg = {
        type: "click",
        action: "Click Me button clicked"
    };

    createEventLog() {
        log(this.msg);
        log('Click Me button clicked!');
        console.log('Event Log created!');
    }
}
As you can see above, I'm passing the msg object to the log() method which consist of two properties: type and action. The type is click and action is Click Me button clicked. As I click the button now, I get two messages from client.js along with my console.log() message as shown below:
This time we have two event logs generated. One is having the message as: "{"type":"click","action":"Click Me button clicked"}" and another one is having the message as "Click Me button clicked!". The log file generated for the same is provided below:
As you can see, this time our whole object is also coming as message in the logs.

This is how, you can log your custom LWC event messages and view them using salesforce event monitoring tool. That's all for this tutorial, I hope you liked it. Let me know your feedback in the comments down below.

Happy Trailblazing!!

Sunday, 31 December 2023

LWC Lookup Component by Salesforce: Lightning Record Picker

Hello Trailblazers,


In this post, we're going to learn about lightning-record-picker which is basically an input field using which you can search for salesforce records. The basic code to implement the same is provided below:

<template>
    <lightning-card hide-header label="Account Record Picker Card">
        <p class="slds-var-p-horizontal_small">
            <lightning-record-picker
                label="Select Account"
                placeholder="Type Something..."
                object-api-name="Account"
            ></lightning-record-picker>
        </p>
    </lightning-card>
</template>


As you can see above, I used the lightning-record-picker tag where I've setup the label as Select Account, the placeholder is Type Something... and the object-api-name is Account. You can ignore the lightning-card. I've added that only for a good white background as we're going to embed this component in our homepage. The result is shown below:



We also did common changes in our meta.xml file to embed this component in our homepage as shown below:

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

Now, it's time to do some more changes. Let's begin!

Add filter to our lookup component

Let's add a default filter to our record picker component such that it'll search only those accounts whose Rating is equal to Warm. It's time to update our js file now:

import { LightningElement } from 'lwc';

export default class RecordPickerDemo extends LightningElement {

    filter = {
        criteria: [
            {
                fieldPath: 'Rating',
                operator: 'eq',
                value: 'Warm'
            }
        ]
    };

}

As you can see above, we defined a filter object that consist of a single property named criteria. This property is an array which can have multiple objects, each having 3 properties:


1. fieldPath: API name of the field for the current object on which we've our record picker. You can also mention relationships upto one level, for example: Parent.Rating (considering the account object)


2. operator: It can have different values depending upon the comparison we want to perform. The possible values are given below:


eq = Equal

ne = Not Equal

lt = Less Than

gt = Greater Than

lte = Less than or equal

gte = Greater than or equal

in = Similar to IN operator of SOQL

nin = Similar to NOT IN operator of SOQL

like = Similar to LIKE operator of SOQL

includes = Check the result should include provided values

excludes = Check the result should exclude provided values


Different keywords provided above are applicable for fields of different data types. The fields and the operator values they support are provided in the salesforce documentation here. If you want to learn more about the keywords, you can check them in the GraphQL documentation here


3. value: Value for the applied filter


The updated html code to apply the filter we defined in js is provided below:

<template>
    <lightning-card hide-header label="Account Record Picker Card">
        <p class="slds-var-p-horizontal_small">
            <lightning-record-picker
                label="Select Account"
                placeholder="Type Something..."
                object-api-name="Account"
                filter={filter}
            ></lightning-record-picker>
        </p>
    </lightning-card>
</template>

Notice that we've populated filter property of our lightning-record-picker with the filter variable that is defined in our js.

With this Rating filter applied by default, we can only see a subset of records. As you can see below, only 4 account records are present in my org with Rating as Warm

If I search in my lookup now, the search is performed with this predefined Rating filter already applied to my record picker:


As you can see above, only these 4 records are visible as I search with keyword o

We can also specify a filterLogic property in our filter object. This property basically consist of logic to combine multiple filter criterias. For example: If in the current filter, we want to consider Cold rating as well, along with Warm rating, we can update our filter as shown below:
import { LightningElement } from 'lwc';

export default class RecordPickerDemo extends LightningElement {

    filter = {
        criteria: [
            {
                fieldPath: 'Rating',
                operator: 'eq',
                value: 'Warm'
            },
            {
                fieldPath: 'Rating',
                operator: 'eq',
                value: 'Cold'
            }
        ],
        filterLogic: '1 OR 2'
    };

}
As you can see above, I've added one more filter criteria that specify the Rating as Cold along with the existing filter for Rating as Warm. Also, I've specified the filterLogic as 1 OR 2, where 1 corresponds to the first filter and 2 corresponds to the second filter, so basically here we're saying that the account rating should either be Warm or Cold. The updated results in our lookup are shown below:

Note: By default, if no filterLogic is defined, all filter criterias are applied using the keyword AND.

Display Additional Field in our Record Picker (Lookup) Search Results

Now, it's time to display an additional field value in our record picker search result. We can display only one field from the same/related object as the additional field. For example: if we want to display the rating as well - in the search results, we can do that as follows:
import { LightningElement } from 'lwc';

export default class RecordPickerDemo extends LightningElement {

    filter = {
        criteria: [
            {
                fieldPath: 'Rating',
                operator: 'eq',
                value: 'Warm'
            },
            {
                fieldPath: 'Rating',
                operator: 'eq',
                value: 'Cold'
            }
        ],
        filterLogic: '1 OR 2'
    };

    displayInfo = {
        additionalFields: ['Rating']
    }

}
If you notice above, we defined one more object named displayInfo. It has a property called additionalFields which is an array with single string value i.e. the API name of our additional field to query which is Rating in our case. I can use this displayInfo object and pass it to our record picker component in the html as shown below:
<template>
    <lightning-card hide-header label="Account Record Picker Card">
        <p class="slds-var-p-horizontal_small">
            <lightning-record-picker
                label="Select Account"
                placeholder="Type Something..."
                object-api-name="Account"
                filter={filter}
                display-info={displayInfo}
            ></lightning-record-picker>
        </p>
    </lightning-card>
</template>
If you noticed above, I added another property to our lightning-record-picker named display-info and it's referring to our displayInfo object which we created in our js. Now, the search results output is also displaying the rating as shown below:


As you can see, we're only getting accounts with rating Warm or Cold.

Can we query using a different field?

By default, records are queried using the name field. However, we can use a different primary field to query records as well. We can also specify an additional field to query records. Let's say we want to query records using Rating field. The updated code for the js file is provided below:

import { LightningElement } from 'lwc';

export default class RecordPickerDemo extends LightningElement {

    filter = {
        criteria: [
            {
                fieldPath: 'Rating',
                operator: 'eq',
                value: 'Warm'
            },
            {
                fieldPath: 'Rating',
                operator: 'eq',
                value: 'Cold'
            }
        ],
        filterLogic: '1 OR 2'
    };

    displayInfo = {
        additionalFields: ['Rating']
    }

    matchingInfo = {
        primaryField: { fieldPath: 'Rating' }
    }
}

If you notice above, I've defined a new object named matchingInfo. In this object, we can define primaryField as well as additionalFields that we want to use to query records. The primaryField is basically an object with single property named fieldPath which should have the API name of the field you want to use. For our example, the API name is Rating for the Rating field of account. Also, the updated html is shown below:

<template>
    <lightning-card hide-header label="Account Record Picker Card">
        <p class="slds-var-p-horizontal_small">
            <lightning-record-picker
                label="Select Account"
                placeholder="Type Something..."
                object-api-name="Account"
                filter={filter}
                display-info={displayInfo}
                matching-info={matchingInfo}
            ></lightning-record-picker>
        </p>
    </lightning-card>
</template>

As you can see above, the matchingInfo property from our js is assigned to matching-info property of our lightning-record-picker.

Now, the records will be queried on the basis of Rating and not Name as shown below:


We can update our code to define an additional field to be used to query records as well. Let's see the updated code:
import { LightningElement } from 'lwc';

export default class RecordPickerDemo extends LightningElement {

    filter = {
        criteria: [
            {
                fieldPath: 'Rating',
                operator: 'eq',
                value: 'Warm'
            },
            {
                fieldPath: 'Rating',
                operator: 'eq',
                value: 'Cold'
            }
        ],
        filterLogic: '1 OR 2'
    };

    displayInfo = {
        additionalFields: ['Rating']
    }

    matchingInfo = {
        primaryField: { fieldPath: 'Rating' },
        additionalFields: [ { fieldPath: 'Phone' } ]
    }
}
As you can see above, we defined another property named additionalFields which is an array where we can define additional fields. We can define only one additional field here. For our use case, I used the Phone field as an additional field. Now, I can also query records using Phone field as shown below:

Making the lightning-record-picker required

We can make our lightning-record-picker required as well using the required attribute. The updated HTML code is provided below:
<template>
    <lightning-card hide-header label="Account Record Picker Card">
        <p class="slds-var-p-horizontal_small">
            <lightning-record-picker
                label="Select Account"
                placeholder="Type Something..."
                object-api-name="Account"
                filter={filter}
                display-info={displayInfo}
                matching-info={matchingInfo}
                required
            ></lightning-record-picker>
        </p>
    </lightning-card>
</template>
As you can see above, I've added the required attribute to my lightning-record-picker tag. This is a boolean attribute so we don't need to specify any value. Just adding required means, it's true. We don't need to add it if we don't want to make our field as required. Now, if we don't select a record, by clicking on the record picker, we'll get the error message as shown below:
As you can see, there is a red asterisk (*) with Select Account label as well which specifies that the field is required.

Setting a default record using record id as the component is loaded

One common use case we can encounter is to setup a default record for our lightning-record-picker as it's loaded initially. We can do that using the value attribute. Let's do some changes in our js first:
import { LightningElement } from 'lwc';

export default class RecordPickerDemo extends LightningElement {

    filter = {
        criteria: [
            {
                fieldPath: 'Rating',
                operator: 'eq',
                value: 'Warm'
            },
            {
                fieldPath: 'Rating',
                operator: 'eq',
                value: 'Cold'
            }
        ],
        filterLogic: '1 OR 2'
    };

    displayInfo = {
        additionalFields: ['Rating']
    }

    matchingInfo = {
        primaryField: { fieldPath: 'Rating' },
        additionalFields: [ { fieldPath: 'Phone' } ]
    }

    recordId = '001H3000002jHRYIA2';
}
As you can see above, I've defined a new property in my js class named recordId. I have hardcoded it's value to the id of an account from my salesforce org. Now, I can update my HTML as well so that this account record is pre-selected in the lookup:
<template>
    <lightning-card hide-header label="Account Record Picker Card">
        <p class="slds-var-p-horizontal_small">
            <lightning-record-picker
                label="Select Account"
                placeholder="Type Something..."
                object-api-name="Account"
                filter={filter}
                display-info={displayInfo}
                matching-info={matchingInfo}
                required
                value={recordId}
            ></lightning-record-picker>
        </p>
    </lightning-card>
</template>
If you notice above, the value attribute of my record picker is assigned the recordId property which I defined in my js file. Now, as the component is loaded initially, the account record with this record id is pre-selected as shown below:

That's all for this tutorial, I hope you liked it. There are also some predefined events linked to lightning-record-picker which you can check in the official documentation here. Let me know if you need a tutorial for the same and I can create one. I will look forward to your feedback in the comments down below.


Happy Trailblazing!!

Tuesday, 31 October 2023

Introducing Mastering Lightning Datatable in Salesforce LWC Course on Udemy

Hello Trailblazers,


I'm super excited to launch my first course on udemy titled Mastering Lightning Datatable in Salesforce LWC. In this course, you'll learn about almost every aspect of lightning datatable from scratch and you'll level up as a salesforce developer. I am giving away 100 free coupons to our amazing community. Fill in the below form and stand a chance to win the course for free!

However, if you're interested to buy this course to get immediate access, you can buy it using this link: https://www.udemy.com/course/mastering-lightning-datatable-in-salesforce-lwc/?referralCode=0354EB92E7BB30C0B980

Happy Trailblazing!!

Monday, 14 August 2023

Lifecycle Hooks in LWC

Hello Trailblazers,


In this post we'll understand different lifecycle hooks of lightning web components.


What is a Lifecycle Hook?

A lifecycle hook is a callback method which is called automatically at some point during a lightning web component's lifecycle. In simple words, I can say: From the moment a component is initialized, till the moment it's removed from the page, there are various important instances that can be used by developers to perform various actions. So, in order to allow the developers to perform those actions, some callback methods are automatically called during these instances.

What are the different lifecycle hooks of lightning web components?

There are mainly 5 lifecycle hooks in lightning web components. They are:

  1. constructor()

  2. connectedCallback()

  3. renderedCallback()

  4. disconnectedCallback()

  5. errorCallback()

There is a great diagram present in the salesforce official documentation showcasing the lifecycle flow or in other words: the order in which these lifecycle hooks are being called. Let's take a look at that below:


This basically means that whenever a parent and child component is rendered on the page, the order of lifecycle hooks will be as follows:

  1. constructor() called on parent
  2. connectedCallback() called on parent
  3. constructor() called on child
  4. connectedCallback() called on child
  5. renderedCallback() called on child
  6. renderedCallback() called on parent

In case of an error in child component, the errorCallback() of parent component will be called after the first 4 callbacks mentioned above i.e. before calling the renderedCallback() for child + parent component. The updated order in case of an error in child will be:

  1. constructor() called on parent
  2. connectedCallback() called on parent
  3. constructor() called on child
  4. connectedCallback() called on child
  5. errorCallback() called on parent
  6. renderedCallback() called on child
  7. renderedCallback() called on parent

Now it's time to get into action and verify whatever we specified above

constructor()

This callback method is called as a component is constructed. The first statement inside a constructor should always be super(). Calling super() will provide the correct value for this keyword which we can use in our lwc. Some more important points about constructor are:

  1. You cannot access public properties (i.e. the properties annotated with @api) within a component's constructor. If you try to refer anything, it'll come as undefined

  2. You cannot access any html tag/element inside the component's constructor as the component is not rendered yet.

  3. You can access private properties of a component inside a constructor

Now the question is: What should we use constructor() for?

Like a normal javascript class, this lwc class constructor can also be used to initialize some private variables you may have. It can be used to perform an operation like: calling your apex method (or any javascript method) to retrieve information or perform a particular action.

Let's have a look at an example lightning component named Child below:

child.js

import { LightningElement, api } from 'lwc';

export default class Child extends LightningElement {

    count = 0;
    @api
    message = 'default';

    constructor() {
        super();
        console.log('Child constructor called');
        console.log(this.count);
        console.log(this.message);
        console.log(this.template.querySelector('lightning-button'));
    }
}
As you can see in the above js file, a constructor is defined. Inside the constructor, first of all calling super(), then we are displaying a text - Child constructor called, then displaying value of private variable count, after that we're displaying value of public variable message and finally we're trying to display the reference to a lightning-button element present in our html.

child.html

We have a lightning-button with label Increase Count in our html file which will call a js method increaseCount() as the button is clicked:
<template>
    <lightning-button label="Increase Count" onclick={increaseCount}></lightning-button>
    <br /><br />
</template>

We'll use this button later in the tutorial. For now, let's see the console logs as this component is rendered:

First we have the message Child constructor called, the value of private variable count is coming as 0 inside the constructor, the value of public variable message is coming as undefined and finally, the reference to lightning-button is also coming as null inside the constructor, because the component is not rendered yet.

connectedCallback()

This callback method is called when a lwc component is inserted into the dom. It establishes communication of the lwc component with the current document or container where it is inserted. Some important points about connected callback are:

  1. connectedCallback() on the parent component will be called before connectedCallback() on the child component is called. This is the reason that you cannot access child components from connectedCallback() of parent, as the child components are not inserted into the dom yet

  2. This callback is invoked after all the public properties are set. This means that whatever initial values are being passed to the public properties of component, the component will have those values assigned when the connectedCallback() is called

  3. This also means that connectedCallback() can only access the INITIAL VALUES OF PUBLIC PROPERTIES i.e. if you're updating the public property after the component is inserted, connectedCallback() will not be called again with the new value of public property. So, if you've a method which should be called based on the value of public property, it's better to call that method in a setter instead of connectedCallback() as the setter will be called again and again whenever the value of public property is set/updated

  4. You can perform some initialization tasks in the connectedCallback() like: listening for events or getting some initial data from the server

  5. connectedCallback() can be called more than 1 time as well. An example scenario can be: when a component is removed from the DOM and inserted again

Let's add connectedCallback() to our Child lwc component now. The updated code is provided below:

child.js

import { LightningElement, api } from 'lwc';

export default class Child extends LightningElement {

    count = 0;
    @api
    message = 'default';

    constructor() {
        super();
        console.log('Child constructor called');
        console.log(this.count);
        console.log(this.message);
        console.log(this.template.querySelector('lightning-button'));
    }

    connectedCallback() {
        console.log('Child connected callback called');
        console.log(this.count);
        console.log(this.message);
        console.log(this.template.querySelector('lightning-button'));
    }
}
As you can see above, I'm trying to access the private property count, public property message and lightning-button again in my connectedCallback(), if you remember in our constructor, we were only able to access the value of private property i.e. count, message was coming as undefined and lightning-button reference was coming as null. Let's see if we're able to access anything else now out of these.
As you can see in the output above, message is coming as default this time instead of undefined. However, the reference to lightning-button is still coming as null. This is because the public properties of our lwc component are now having the initial value assigned to them. As lightning-button is a child component with respect to our child lwc component, it's still not connected to the DOM and therefore is coming as null (remember the first point specified above: connectedCallback() on the parent is called before connectedCallback() on the child and here child component is lightning-button).

Let's create a parent component as well and pass the value to our message (public property) from there to ensure it gets reflected in our connectedCallback() as well. Our parent component's name is Parent and the code for the same is provided below:

parent.html

<template>
    <c-child message="hello"></c-child>
</template>
As you can see above, we're passing the value of message variable as hello. Let's take a look at the js file as well

parent.js

import { LightningElement } from 'lwc';

export default class Parent extends LightningElement {

    constructor() {
        super();
        console.log('Parent constructor called');
    }

    connectedCallback() {
        console.log('Parent connected callback called');
        console.log(this.template.querySelector('c-child'));
    }

    renderedCallback() {
        console.log('Parent rendered callback called');
        console.log(this.template.querySelector('c-child'));
    }
}
Here also, I've defined a constructor and a connectedCallback() as well to see in which order the parent child lifecycle hooks are being called. I also added a renderedCallback(). This renderedCallback() method should be called post connectedCallback() is called on child as per the order. We'll learn more about renderedCallback() in a bit, I've added this here for a reason. Let's have a look at the updated console.log() now:


The order of events is shown below:

  1. Parent constructor() is called

  2. Parent connectedCallback() is called and the reference to child component c-child is coming as null inside it

  3. Child constructor() is called. Value of count is 0, message is undefined and reference to lightning-button is coming as null as it's further a child component for our child lwc.

  4. Child connectedCallback() is called where value of count is 0. Notice that the value of message is hello instead of default this time because this is the value which is passed from our parent lwc to child lwc for this public property, reference to lightning-button is still coming as null

  5. Finally, our renderedCallback() method is called in our parent component as per the order and as it's called after the child's connectedCallback() method, this means that the child lwc is now connected to the DOM. Therefore, reference to child lwc is not coming as null this time, as it was coming in the parent's connectedCallback() method.

I hope all of this is clear. Now, let's move on to the renderedCallback() method.

renderedCallback()

As the name suggests, this callback method is called once the component has rendered. As the component can render multiple times, this callback method will also be called each time the component is re-rendered. Some important points about renderedCallback() are:

  1. renderedCallback() on the child component is called before it's called on the parent component

  2. Whenever the component renders, the expressions used in a template are re-evaluated. This means that if we've created a getter method which is used in our html file and that getter is returning a dynamic value based on some properties, it'll be re-evaluated as well

  3. Whenever the component's state is changed, the component will be re-rendered

  4. When a component is re-rendered, the lwc engine attempts to reuse the existing elements. For example: if you update something in the parent component due to which the parent re-renders, it'll not re-render the child component. Another example can be: if you're displaying a list of child components and if you re-order the list, then although the components are placed at different positions now, they're not re-rendered. The engine assumes that the same components are being used and just placed at a different positions now, so they're not re-rendered

    However, if you use a different key/update the key of child component, it might get re-rendered - I'm not going to show a demo of this, this is your homework. Try and let me know how it works in the comments down below!

  5. As I specified in point 3, whenever the component's state is changed, the component will be re-rendered. Therefore, we need to make sure that we don't update the state of the component (for example: a property which is being displayed in component's html) in the renderedCallback() itself as it'll re-render the component and will call renderedCallback() again. In this case, the renderedCallback() will be called again and again recursively which will result in an infinite loop

We've already defined renderedCallback() in our parent component as specified in the connectedCallback() section. Let's define renderedCallback() in our child lwc as well.

child.js

import { LightningElement, api } from 'lwc';

export default class Child extends LightningElement {

    count = 0;
    @api
    message = 'default';

    constructor() {
        super();
        console.log('Child constructor called');
        console.log(this.count);
        console.log(this.message);
        console.log(this.template.querySelector('lightning-button'));
    }

    connectedCallback() {
        console.log('Child connected callback called');
        console.log(this.count);
        console.log(this.message);
        console.log(this.template.querySelector('lightning-button'));
    }

    renderedCallback() {
        console.log('Child rendered callback called');
        console.log(this.template.querySelector('lightning-button'));
    }
}
Let's have a look at the updated logs now:


The order  of events is provided below:

  1. Parent constructor called

  2. Parent connected callback called where reference to child lwc is coming as null

  3. Child constructor called where count is 0, message is undefined and reference to lightning-button is coming as null

  4. Child connected callback called where count is 0, message is hello and reference to lightning-button is again coming as null

  5. Child rendered callback called where reference to lightning-button is coming properly as the lightning-button is connected to the DOM now

  6. At last, parent rendered callback is called where reference to child lwc is coming properly as the child lwc is connected to DOM now

We are now going to re-render the parent lwc to see when renderedcallback() is called in parent and child LWCs. Let's add some more code!

If you remember, our child lwc html is calling a method increaseCount() when the lightning-button is clicked, let's add that method to our child lwc js file as shown below:
increaseCount() {
    this.dispatchEvent(new CustomEvent('increasecount', {
        detail: {
            message: 'Increased count to ' + (++this.count)
        }
    }));
}
This method will fire an event named increasecount whenever the button is clicked which will contain a message with the value of count variable increased by 1. The full code of child.js is provided below:

child.js

import { LightningElement, api } from 'lwc';

export default class Child extends LightningElement {

    count = 0;
    @api
    message = 'default';

    constructor() {
        super();
        console.log('Child constructor called');
        console.log(this.count);
        console.log(this.message);
        console.log(this.template.querySelector('lightning-button'));
    }

    connectedCallback() {
        console.log('Child connected callback called');
        console.log(this.count);
        console.log(this.message);
        console.log(this.template.querySelector('lightning-button'));
    }

    renderedCallback() {
        console.log('Child rendered callback called');
        console.log(this.template.querySelector('lightning-button'));
    }

    increaseCount() {
        this.dispatchEvent(new CustomEvent('increasecount', {
            detail: {
                message: 'Increased count to ' + (++this.count)
            }
        }));
    }
}
Notice the increaseCount() added at the end. Let's update our parent.html file as well now:

parent.html

<template>
    <lightning-card title={message}>
        <p class="slds-var-p-around_small">
            <c-child onincreasecount={updateMessage} message="hello"></c-child>
        </p>
    </lightning-card>
</template>
As you can see above, I've covered my child lwc with a lightning card which is displaying the value of message variable as title. I'm also capturing the increasecount event and calling another method in my parent.js named updateMessage() which will update the value of message variable displayed in the card title. Finally, let's take a look at our updateMessage() defined in parent.js as well:
updateMessage(event) {
    this.message = event.detail.message;
}
As you can see above, it's updating the message variable with the value of message coming from the event. This message variable will be displayed as the title of lightning-card. Let's have a look at the full code below:

parent.js

import { LightningElement, track } from 'lwc';

export default class Parent extends LightningElement {

    message = 'Updated count will appear here!';

    constructor() {
        super();
        console.log('Parent constructor called');
    }

    connectedCallback() {
        console.log('Parent connected callback called');
        console.log(this.template.querySelector('c-child'));
    }

    renderedCallback() {
        console.log('Parent rendered callback called');
        console.log(this.template.querySelector('c-child'));
    }

    updateMessage(event) {
        this.message = event.detail.message;
    }
}
Notice the default value of message variable as: Updated count will appear here!. I've defined the updateMessage() method at the end which is updating the value of this message variable. Let's take a look at the component in action:


As we click the Increase Count button present in child lwc, it fires an event with updated value of count. This increasecount event is captured by parent lwc and it'll update the value of message variable shown as a title of lightning-card as shown above.

The thing to notice here is that, each time we click the button and the event is fired, it re-renders the parent component as shown below:


Notice that only the parent lwc's rendered callback is called again and again and not the child one as I increased count from 1 to 5. This means that even though the parent is rendered multiple times, the child LWC is just reused as there's no change in the state of child lwc. It's still showing the same Increase Count button. This covers our point 3 and 4  under important points about renderedCallback(). It's time to move on to the next callback method now i.e. disconnectedCallback()

disconnectedCallback()

disconnectedCallback() will be called whenever the component is disconnected from the DOM, it's mainly useful to clean up the work done in connectedCallback(). You can use it for simple purposes like to remove cache or event listeners. Let's define disconnectedCallback() on our child component js. You can simply add the below method:
disconnectedCallback() {
    console.log('Child disconnected callback called');
}

Our updated child.js file is shown below:

child.js

import { LightningElement, api } from 'lwc';

export default class Child extends LightningElement {

    count = 0;
    @api
    message = 'default';

    constructor() {
        super();
        console.log('Child constructor called');
        console.log(this.count);
        console.log(this.message);
        console.log(this.template.querySelector('lightning-button'));
    }

    connectedCallback() {
        console.log('Child connected callback called');
        console.log(this.count);
        console.log(this.message);
        console.log(this.template.querySelector('lightning-button'));
    }

    renderedCallback() {
        console.log('Child rendered callback called');
        console.log(this.template.querySelector('lightning-button'));
    }

    disconnectedCallback() {
        console.log('Child disconnected callback called');
    }

    increaseCount() {
        this.dispatchEvent(new CustomEvent('increasecount', {
            detail: {
                message: 'Increased count to ' + (++this.count)
            }
        }));
    }
}
Notice the disconnectedCallback() added above the increaseCount() and below renderedCallback(). Let's update our parent component a little bit as well to make sure we're able to disconnect child lwc from the DOM.

Updated parent.html file is provided below:

parent.html

<template>
    <lightning-card title={message}>
        <p class="slds-var-p-around_small">
            <template if:true={show}>
                <c-child onincreasecount={updateMessage} message="hello"></c-child>
            </template>
            <lightning-button label="Toggle Child" onclick={toggleChild}></lightning-button>
        </p>
    </lightning-card>
</template>

As you can see above, I've added a template tag with if:true condition which is checking a boolean variable named show. Only when this variable is true, our child component will be displayed to us. I'm going to create this variable in our parent.js file. I've added another lightning-button with label Toggle Child which is calling the toggleChild() when clicked. On click of this button, I'm going to toggle the value of show variable from true -> false or from false -> true which will hide/show the child lwc component. This will utlimately call our disconnectedCallback() on our child lwc as well. Let's take a look at the updated parent.js now:

parent.js

import { LightningElement, track } from 'lwc';

export default class Parent extends LightningElement {

    message = 'Updated count will appear here!';
    show = true;

    constructor() {
        super();
        console.log('Parent constructor called');
    }

    connectedCallback() {
        console.log('Parent connected callback called');
        console.log(this.template.querySelector('c-child'));
    }

    renderedCallback() {
        console.log('Parent rendered callback called');
        console.log(this.template.querySelector('c-child'));
    }

    updateMessage(event) {
        this.message = event.detail.message;
    }

    toggleChild() {
        this.show = !this.show;
    }
}
As you can see above, I've added show variable below message variable whose default value is true. I've also added another method named toggleChild() at the end. This method will be called when we click the Toggle Child lightning button and it'll toggle the value of show variable from true to false and from false to true.

This toggling will automatically hide/show the child lwc or I can say connect/disconnect child lwc from the DOM. Let's take a look at the component in action first:
As you can see in the above demo, first of all I increased the count using Increase Count lightning-button in the child component to 2. Then I clicked on Toggle Child button which removed the child component from the DOM. I brought it back by clicking the Toggle Child button again and then I clicked on Increase Count button again which increased the value of count starting from 1 to 5. It started from 1 again as the child lwc is reinitialized and therefore is having the default value of count as 0. Let's take a look at related logs now.

After the components were loaded initially and I clicked on Increase Count button twice and then the Toggle Child button which removed the child lwc from DOM. The console.log statements for these 3 operations are shown below:


As you can see, for the first two operations, when count is increased, parent renderedCallback() is called and I can refer the child lwc easily as it's connected to the DOM. Then I clicked Toggle Child button, it called child's disconnectedCallback() and we have the statement: Child disconnected callback called printed to the console. It also called parent's renderedCallback() as the child is removed from the DOM so the parent is also re-rendered. Notice that this time, the child lwc reference in the parent's renderedCallback() is coming as null as the child component is no more connected to the DOM.

Let's click the Toggle Child button again now:


As you can see above, as I clicked on Toggle Child button again, the child lwc is again connected to DOM. Therefore, the child constructor() is called again, then child connectedCallback() is called, then renderedCallback() and finally parent's renderedCallback() is called once again and this time the reference to child lwc is not coming as null.

Post that, I clicked on Increase Count button 5 more times, the count is increased from 1 to 5 and the parent lwc is rendered 5 times as shown below:

errorCallback()

Now, let's take a look at our last method in the lwc lifecycle i.e. errorCallback(). This callback method will be called whenever an error occurs in lifecycle hook and it captures errors in all the child (descendent) components in it's tree. Let's understand with an example. I'm going to throw error from the connectedCallback() of my child.js file. I'll also define errorCallback() methods in both child and parent lwc to understand which method is being called and the information received in the errorCallback() method. Let's update our child lwc first.

child.js

import { LightningElement, api } from 'lwc';

export default class Child extends LightningElement {

    count = 0;
    @api
    message = 'default';

    constructor() {
        super();
        console.log('Child constructor called');
        console.log(this.count);
        console.log(this.message);
        console.log(this.template.querySelector('lightning-button'));
    }

    connectedCallback() {
        console.log('Child connected callback called');
        console.log(this.count);
        console.log(this.message);
        console.log(this.template.querySelector('lightning-button'));
        let error = {
            code: 100,
            message: 'Error from child connected callback!'
        };
        throw error;
    }

    renderedCallback() {
        console.log('Child rendered callback called');
        console.log(this.template.querySelector('lightning-button'));
    }

    disconnectedCallback() {
        console.log('Child disconnected callback called');
    }

    errorCallback(error, stack) {
        console.log('Child error callback called, error = ' + JSON.stringify(error) + ', stack = ' + JSON.stringify(stack));
    }

    increaseCount() {
        this.dispatchEvent(new CustomEvent('increasecount', {
            detail: {
                message: 'Increased count to ' + (++this.count)
            }
        }));
    }
}
As you can see above, I've updated the connectedCallback(). I'm also showing this update again below:
connectedCallback() {
    console.log('Child connected callback called');
    console.log(this.count);
    console.log(this.message);
    console.log(this.template.querySelector('lightning-button'));
    let error = {
        code: 100,
        message: 'Error from child connected callback!'
    };
    throw error;
}
I've added 4 more lines after console.log statements where I'm defining an error object with two properties, code and message. Then I'm throwing that error object. I also defined errorCallback() method as shown in the below snippet:
errorCallback(error, stack) {
    console.log('Child error callback called, error = ' + JSON.stringify(error) + ', stack = ' + JSON.stringify(stack));
}

errorCallback() has two parameters:
  1. error: This is the JavaScript native error object. It's the error which was thrown by component where it occured. In our case it'll be the error object we're throwing which is having two properties: code and message.

  2. stack: This is a string specifying - in which component the error occured. It'll show path from the component whose errorCallback() was called till the child component where error was thrown

Let's add the errorCallback() in parent lwc as well. I'm going to add the below method to parent.js:
errorCallback(error, stack) {
    console.log('Parent error callback called, error = ' + JSON.stringify(error) + ', stack = ' + stack);
    console.log(this.template.querySelector('c-child'));
}
Let's take a look at the full parent.js file as well after updates:

parent.js

import { LightningElement, track } from 'lwc';

export default class Parent extends LightningElement {

    message = 'Updated count will appear here!';
    show = true;

    constructor() {
        super();
        console.log('Parent constructor called');
    }

    connectedCallback() {
        console.log('Parent connected callback called');
        console.log(this.template.querySelector('c-child'));
    }

    renderedCallback() {
        console.log('Parent rendered callback called');
        console.log(this.template.querySelector('c-child'));
    }

    errorCallback(error, stack) {
        console.log('Parent error callback called, error = ' + JSON.stringify(error) + ', stack = ' + stack);
        console.log(this.template.querySelector('c-child'));
    }

    updateMessage(event) {
        this.message = event.detail.message;
    }

    toggleChild() {
        this.show = !this.show;
    }
}
Notice the errorCallback() added below renderedCallback(). Now, let's take a look at the console statements as the components are loaded to understand how errorCallback() is being called:


A couple of things to notice above:

  1. Only the parent errorCallback() is called and not the errorCallback() present in the child lwc

  2. Error object is received in the errorCallback() which is exactly the same as thrown by the child lwc. The stack string received in the errorCallback() is showing the stack/path from the parent lwc (the component whose errorCallback() is called) to child lwc (where the error was thrown) as: <c-parent> <c-child>

  3. I am trying to display a reference to child lwc in the errorCallback() as well and it's working fine. This means that once the child lwc is connected to the DOM it can be referred in any of the callback methods be it errorCallback() or renderedCallback()

Let's take a look at the order in which the callback methods are executed as well:

  1. Parent: constructor() called

  2. Parent: connectedCallback() called (reference to child lwc is null)

  3. Child: constructor() called (count is coming as 0, message as undefined and reference to lightning-button is coming as null)

  4. Child: connectedCallback() called (count is coming as 0, message as hello and reference to lightning-button is still null as lightning-button is not connected to DOM yet). This callback method is also throwing error now

  5. Parent: errorCallback() called (child lwc can now be referenced as it's now connected to DOM)

  6. Child: renderedCallback() called (reference to lightning-button is coming properly now as lightning-button is now connected to DOM)

  7. Parent: renderedCallback() called (child lwc can now be referenced here as well because it's now connected to DOM)

So that's the final series of events/callbacks we have for this post in our demo components.

We covered all the callback methods/lifecycle hooks of lwc in this post.

That's all for this tutorial everyone, I hope you liked it. Let me know your feedback in the comments down below.

Happy Trailblazing!!