Hi Developer, As we all know that LWC is a complete new way to develop the Lightning component and there are very limited no of resources to learn LWC.
So, here is this tutorial we are going to learn how we can create a Custom Reusable Lookup based on user input.
Setp1:- Create searchComponent
This component is the one, who enables the search functionality. Use the below code for HTML markup and JavaScript.
<template>
<div class="slds-grid slds-wrap">
<div class="slds-col slds-size_4-of-4">
<div>
<!-- Create an Input Field where user can enter text to find the
Records-->
<lightning-input variant="label-hidden"
label="Search Record" value={searchKey} type="search"
onchange={handleChange} placeholder="type text here">
</lightning-input>
</div>
</div>
</div>
</template>
searchComponent.js
import { LightningElement, track } from 'lwc';
export default class SearchComponent extends LightningElement {
@track searchKey;
handleChange(event){
/* eslint-disable no-console */
//console.log('Search Event Started ');
const searchKey = event.target.value;
/* eslint-disable no-console */
event.preventDefault();
const searchEvent = new CustomEvent(
'change',
{
detail : searchKey
}
);
this.dispatchEvent(searchEvent);
}
}
Step2: – Create recordList component
In this component, we will display the list of all the records which will be returned matching with the entered text. Here is the code for HTML markup & JavaScript Class.
<template>
<!--<a href="JavaScript:Void(0)" style="text-decoration:none;" onclick={handleSelect}>
<lightning-layout>
<lightning-layout-item>
<lightning-icon icon-name={iconname} size="small"></lightning-icon>
 Â
</lightning-layout-item>
<lightning-layout-item>
{record.Name}
</lightning-layout-item>
</lightning-layout>
</a> -->
<div >
<div class="slds-grid slds-wrap
slds-dropdown_length-with-icon-7
slds-dropdown_fluid
slds-p-left_small"
>
<div class="slds-col slds-size_4-of-4 ">
<ul class="slds-listbox slds-listbox_vertical" role="presentation">
<li role="presentation" class="slds-listbox__item">
<div class="slds-media slds-listbox__option
slds-listbox__option_entity
slds-listbox__option_has-meta"
role="option"
onclick={handleSelect}>
<span class="slds-media__figure slds-listbox__option-icon">
<lightning-icon icon-name={iconname} size="small"></lightning-icon>
</span>
<span class="slds-media__body"
style="padding-top: 9px;font-weight: 600;">
<span class="slds-listbox__option-text
slds-listbox__option-text_entity">
{record.Name}
</span>
</span>
</div>
</li>
</ul>
</div>
</div>
</div>
</template>
recordList.js
import { LightningElement, api } from 'lwc';
export default class RecordList extends LightningElement {
@api record;
@api fieldname;
@api iconname;
handleSelect(event){
event.preventDefault();
const selectedRecord = new CustomEvent(
"select",
{
detail : this.record.Id
}
);
/* eslint-disable no-console */
//console.log( this.record.Id);
/* fire the event to be handled on the Parent Component */
this.dispatchEvent(selectedRecord);
}
}
Step3: – Create customLookup Component
Main component which will contain both the component searchComponent and recordList component and also, responsible for calling the apex method.
<template>
<template if:false={selectedRecord}>
<div class="slds-p-around_x-small">
<c-search-component
onchange={handleOnchange}>
</c-search-component>
</div>
</template>
<div >
<template if:true={error}>
<template if:true={error.details}>
<template if:true={error.details.body}>
{error.details.body.message}
</template>
</template>
</template>
</div>
<div>
<template if:false={selectedRecord}>
<template if:true={records}>
<template for:each={records} for:item="record">
<c-record-list key={record.Id} record={record}
onselect={handleSelect} iconname={iconname}
fieldname={searchfield}>
</c-record-list>
</template>
</template>
</template>
<template if:false={selectedRecord}>
</template>
</div>
<div class="slds-p-around_x-small">
<template if:true={selectedRecord}>
<div class="slds-combobox__form-element slds-input-has-icon
slds-input-has-icon_left-right" role="none">
<span class="slds-icon_container
slds-icon-standard-account
slds-combobox__input-entity-icon" title="Account">
<lightning-icon icon-name={iconname} ></lightning-icon>
</span>
<input class="slds-input slds-combobox__input
slds-combobox__input-value"
id="combobox-id-5" aria-controls="listbox-id-5"
autocomplete="off" role="textbox" type="text"
placeholder="Select an Option" readonly=""
value={selectedRecord.Name}
disabled
/>
<button class="sicon_container slds-button slds-button_icon
slds-input__icon slds-input__icon_right"
title="Remove selected option"
onclick={handleRemove}>
<lightning-icon icon-name="utility:close" size="small">
</lightning-icon>
<span class="slds-assistive-text">Remove selected option</span>
</button>
</div>
<!--<lightning-layout>
<lightning-layout-item>
<lightning-pill label={selectedRecord.Name} onremove={handleRemove}>
<lightning-icon icon-name={iconname}></lightning-icon>
</lightning-pill>
</lightning-layout-item>
</lightning-layout> -->
</template>
</div>
</template>
customLookup.js
import { LightningElement, track, api } from 'lwc';
import findRecords from '@salesforce/apex/CustomLookupController.findRecords';
export default class CustomLookup extends LightningElement {
@track records;
@track error;
@track selectedRecord;
@api index;
@api relationshipfield;
@api iconname = "standard:account";
@api objectName = 'Account';
@api searchfield = 'Name';
/*constructor(){
super();
this.iconname = "standard:account";
this.objectName = 'Account';
this.searchField = 'Name';
}*/
handleOnchange(event){
//event.preventDefault();
const searchKey = event.detail.value;
//this.records = null;
/* eslint-disable no-console */
//console.log(searchKey);
/* Call the Salesforce Apex class method to find the Records */
findRecords({
searchKey : searchKey,
objectName : this.objectName,
searchField : this.searchfield
})
.then(result => {
this.records = result;
for(let i=0; i < this.records.length; i++){
const rec = this.records[i];
this.records[i].Name = rec[this.searchfield];
}
this.error = undefined;
//console.log(' records ', this.records);
})
.catch(error => {
this.error = error;
this.records = undefined;
});
}
handleSelect(event){
const selectedRecordId = event.detail;
/* eslint-disable no-console*/
this.selectedRecord = this.records.find( record => record.Id === selectedRecordId);
/* fire the event with the value of RecordId for the Selected RecordId */
const selectedRecordEvent = new CustomEvent(
"selectedrec",
{
//detail : selectedRecordId
detail : { recordId : selectedRecordId, index : this.index, relationshipfield : this.relationshipfield}
}
);
this.dispatchEvent(selectedRecordEvent);
}
handleRemove(event){
event.preventDefault();
this.selectedRecord = undefined;
this.records = undefined;
this.error = undefined;
/* fire the event with the value of undefined for the Selected RecordId */
const selectedRecordEvent = new CustomEvent(
"selectedrec",
{
detail : { recordId : undefined, index : this.index, relationshipfield : this.relationshipfield}
}
);
this.dispatchEvent(selectedRecordEvent);
}
}
Step4:- Create CustomLookupController Apex class
public with sharing class CustomLookupController {
public CustomLookupController() {
}
@AuraEnabled(cacheable = true)
public static List<SObject> findRecords(String searchKey, String objectName, String searchField){
String key = '%' + searchKey + '%';
String QUERY = 'Select Id, '+searchField+' From '+objectName +' Where '+searchField +' LIKE :key';
System.debug(System.LoggingLevel.DEBUG, QUERY);
List<SObject> sObjectList = Database.query(QUERY);
return sObjectList;
}
}
Step5 :- Create Demo App and test lookup
<aura:application extends="force:slds">
<c:customLookup iconname="standard:case"
objectName="Case"
searchfield="CaseNumber"/>
</aura:application>

If you have any Queries or Suggestions DM me @cloudyamit or email me @ [email protected]
Happy Learning  🙂 😉
#SFDCPanther #Trailblazer
28 comments
Hi Amit,
this is really useful, this is same as Lightning Component that you have created.
Thanks 🙂
Awesome. Appreciate your help!!
What is the easiest way to add a “new record” to this lookup component?
For this you need to add one more action to the List say it New Record and once use clicks on that open a pop for creating a new record and after saving the record get the Record Id, dispatch an event and then handle in the parent component. Seems complex yes it is but this is the approach you can follow.
step5 is not working for lightning web component please help me for this
Hey Ankit,
Are you getting any Error? More information will be great so that I can help you in a better way.
Thanks & Regards,
SFDCPanther
Hi Amit,
How do I use this component inside a LWC.
I called customLookup and passed three values which you are passing in app, but it is not appearing in my app page.
Pls help.
Hi Saurav,
Below is the code to call the component
In Aura Component OR Aura Application
In LWC Component
Change the properties if Differs
Thanks & Regards,
SFDCPanther
Hi, Where’s the code?
I’m having the same issue, all the code is there, but lookup doesn’t appear
Hi,
This is the complete code that I do have Soon I will upload it to Github and then Will share the Link here.
In between can you share the error so that I can help you!
Thanks & Regards,
SFDCPanther
If you delete the annotation (cacheable = true) search will be able to search starting from the first character set
Amit,
Great work on this. One small suggestion for your Demo App is to show how to retrieve the data:
DemoApp:
DemoAppController.js
({
handleSelectedRec : function(component, event, helper)
{
console.log(“Selected Record Id: ” + event.getParam(“recordId”));
}
})
I didn’t what you are trying to convey here?
Hello SFDC Panther,
First of all, thanks for a good post to do this workaround for lookup fields.
I can see that you tried to create an Aura Application in the last step, in order to showcase the custom lookup component to drive the functionality.
Instead of that, i created a LWC itself having the following code:
Then i displayed the LWC on the Lightning App Page (One Region)
However, i noticed, that when i am typing some keyword, it does not do a search, of records, but whenever i press a backspace, i would get the list of records.
For Example:
If i open the page and type keyword “Uni” to search for list of records, i wont get any search results, but whenever i press the backspace on the keyboard resulting in searching for “Un” then the list of records appear.
I noticed that there is some issue here:
.then(result => {
this.records = result;
for(let i=0; i < this.records.length; i++) {
this.records[i].Name = this.records[i][this.searchfield];
}
In the custom lookup js class.
I wanted to know if you came across this?
Thanks mate!
Hey Rajat,
I used application to show the output as we can not create the application in LWC and you used the component in Lightning Page in both ways the goal is being achieved 🙂
Thanks,
SFDCPanther
we can make use of map function in order to fix that issue
.then(result => {
this.records = result;
if (result && result.length){
this.records = result.map((rec)=> {
return {
...rec,
Name: rec[this.searchfield]
}
});
}
// for (let i = 0; i < this.records.length; i++) {
// const rec = this.records[i];
// this.records[i].Name = rec[this.searchfield];
// }
this.error = undefined;
//console.log(' records ', this.records);
})
Which issue are we talking about?
Further to my comment before, i noticed, that if you remove the (cacheable=true) in the Apex class, the code starts working, without giving the backspace in order to create results (you may want to check this, by just using LWC instead of an aura application right at the end.)
The other question i still have is, that whenever events are dispatched from the search component captured by the custom component, it appears that the event is being captured twice and not once.
Let me know if there is any way to share screen shots of the console log of the screen
Yeah, I removed cacheable and it starts working thanks for the catch. For the event thing, it’s very strange but it might be as the event is being dispatched on the change of the input so every time you type an input it will dispatch the event. Let me know if this is the case.
Thanks,
SFDCPanther
Hi,
I’m new with event handling.
I know when we dispatch event from a child component, parent component get that event.
But here in the Custom Lookup component, we are dispatching a custom event: this.dispatchEvent(selectedRecordEvent);
What is this for?
Thanks in Advance
Hey Jayant,
Once we will be using this component inside any component in real-time then we need to get the selected record Id. This event will be handled by the parent component of Custom Lookup Component.
Hope this clears your doubt.
Thanks & Regards,
SFDCPanther
I want to include this component in my html table. I also need the index of row of which it was changed.
Hi Anuj,
You can definitely do that but for that, you have to make the changes in the List Component and in the Parent Component. In the child, component sends the Index no while using a component inside the parent component and while firing the event on select send the index no as well.
What is this line, getting error in <c tag, i am new to LWC
Your message is not clear.
How would I go about returning additional fields in addition to the name? I have added MailingAddress to the Query but Im not sure how to get these values to show up in the list of records
Hi David,
You need to make some changes at JavaScript side to show that field.