Are you looking for adding or removing rows dynamically in Lightning Web Component to create records or do something else? In this blog, I am going to demonstrate adding or removing rows dynamically to create Contact records in bulk using Lightning Data Service. You will also learn accessing the html element and access public methods of those html elements.
Business use case
Your team wants an ability to add contacts in bulk in one go by using adding or removing rows dynamically. The standard New Contact button in Salesforce gives you the ability to add one at a time and its time consuming.
Implementation – adding or removing rows dynamically
Let’s create a custom Lightning Web Component in our Visual Studio Code. Create a Lightning Web Component with name “dynamicRecordCreationRows“.The Lightning Web Component will have generally three files. They are html, js and meta-xml.js.

dynamicRecordCreationRows.html
The html file has used for:each iterator to iterator over the array controlled by button to add or remove rows in each rows. The input fields which are part of lighting-record-edit-form placed in lightning-layout component. A lightning-layout
is a flexible grid system for arranging containers within a page or inside another container. The default layout is mobile-first and can be easily configured to work on different devices. To understand these concepts in a better way, check these links:-

<template>
<lightning-card>
<h3 slot="title">
<lightning-icon icon-name="standard:timesheet" alternative-text="Event" size="small"></lightning-icon>
Bulk Contact Creation
</h3>
<template for:each={itemList} for:item="item" for:index="index">
<lightning-record-edit-form key={item.id} object-api-name="Contact">
<lightning-messages> </lightning-messages>
<lightning-layout multiple-rows>
<lightning-layout-item size="12" small-device-size="6" medium-device-size="4" large-device-size="2"
padding="around-small">
<lightning-input-field field-name="FirstName" variant="label-stacked" required>
</lightning-input-field>
</lightning-layout-item>
<lightning-layout-item size="12" small-device-size="6" medium-device-size="4" large-device-size="2"
padding="around-small">
<lightning-input-field field-name="LastName" variant="label-stacked" required>
</lightning-input-field>
</lightning-layout-item>
<lightning-layout-item size="12" small-device-size="6" medium-device-size="4" large-device-size="2"
padding="around-small">
<lightning-input-field field-name="Title" variant="label-stacked" required>
</lightning-input-field>
</lightning-layout-item>
<lightning-layout-item size="12" small-device-size="6" medium-device-size="4" large-device-size="2"
padding="around-small">
<lightning-input-field field-name="Email" variant="label-stacked" required>
</lightning-input-field>
</lightning-layout-item>
<lightning-layout-item size="12" small-device-size="6" medium-device-size="4" large-device-size="2"
padding="around-small">
<lightning-input-field field-name="AccountId" variant="label-stacked" required>
</lightning-input-field>
</lightning-layout-item>
<lightning-layout-item size="12" small-device-size="6" medium-device-size="4" large-device-size="2"
padding="around-small">
<div class="slds-p-top_medium">
<lightning-icon icon-name="action:new" access-key={item.id} id={index}
alternative-text="Add Row" size="small" title="Add Row" onclick={addRow}>
</lightning-icon>
<lightning-icon icon-name="action:delete" access-key={item.id} id={index}
alternative-text="Delete Row" size="small" title="Delete Row" onclick={removeRow}>
</lightning-icon>
</div>
</lightning-layout-item>
</lightning-layout>
</lightning-record-edit-form>
</template>
</br>
<lightning-layout>
<div class="slds-align_absolute-center">
<lightning-button variant="success" onclick={handleSubmit} name="submit" label="Submit">
</lightning-button>
</div>
</lightning-layout>
</lightning-card>
</template>
dynamicRecordCreationRows.js
The JavaScript file of the component has the method defined to handle the button click. We have three methods defined each for adding rows, removing rows and saving records.
- addRow – This method adds an value using concat method to the array so that rows gets added.
- removeRow – This method removes the value using array filter method. As a result, the rows gets removed.
- handleSubmit – This methods first validates the input fields and then insert the records using Lightning Data Service submit() method.
import { LightningElement, track } from 'lwc';
import { NavigationMixin } from 'lightning/navigation';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
export default class DynamicRecordCreationRows extends NavigationMixin(LightningElement) {
keyIndex = 0;
@track itemList = [
{
id: 0
}
];
addRow() {
++this.keyIndex;
var newItem = [{ id: this.keyIndex }];
this.itemList = this.itemList.concat(newItem);
}
removeRow(event) {
if (this.itemList.length >= 2) {
this.itemList = this.itemList.filter(function (element) {
return parseInt(element.id) !== parseInt(event.target.accessKey);
});
}
}
handleSubmit() {
var isVal = true;
this.template.querySelectorAll('lightning-input-field').forEach(element => {
isVal = isVal && element.reportValidity();
});
if (isVal) {
this.template.querySelectorAll('lightning-record-edit-form').forEach(element => {
element.submit();
});
this.dispatchEvent(
new ShowToastEvent({
title: 'Success',
message: 'Contacts successfully created',
variant: 'success',
}),
);
// Navigate to the Account home page
this[NavigationMixin.Navigate]({
type: 'standard__objectPage',
attributes: {
objectApiName: 'Contact',
actionName: 'home',
},
});
} else {
this.dispatchEvent(
new ShowToastEvent({
title: 'Error creating record',
message: 'Please enter all the required fields',
variant: 'error',
}),
);
}
}
}
dynamicRecordCreationRows.js-meta.xml
The meta file defines the metadata of the component like where it can be used. In our case, we have exposed the component to be used with Home, Record and App page in Lightning Experience.
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>48.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__AppPage</target>
<target>lightning__RecordPage</target>
<target>lightning__HomePage</target>
</targets>
</LightningComponentBundle>
Let’s say I want the LWC on the Account Record. How do I make the lookup defaulted to the AccountId when I bulk insert Multiple Contacts?
I used the same code but i am getting an error while execute the process
did you get any solution??/. i am looking for the same.
Can I convert this to screen flow component?
I like the idea in this article, thanks for sharing over the blog, but I have the below question:
How to add conditional rendering for each record/row, for eg:- If title is ‘Salesforce’ display custom field 1, If title is ‘Google’ display custom field 2, ..etc.
The issue I am facing currently is, if I am changing at one record same action(conditional rendering) action is taking place for rest of the all records/rows in the form(dynamicRecordCreationRows).
nice post
Nice post. But how can we hide labels for each row.
Hi Bro, nice post.
its working fine in desktop mode. while testing in mobile are tablet mode, UI is automatically switched to classic mode not sure why this is happening.
if you have some free time. test it once.
Hi Krishna,
You are right!!! Sanket did you find any solution for that please do let us know.
Thanks
Hi,
Great example to start with.
Instead of having Add/Remove buttons on each row, I added Add Row and Delete Row buttons at the bottom (outside of the lightning-record forms. I added a checkbox column and when user selects checkbox, I am looping over rows and when matched record found, I am deleting record and then calling refreshApex.
Its working in the backend (rows deleting), but the component not refreshing properly (it refreshes sometimes and not some always).
Also when it refreshes the checkbox is not clearing unless I removed last item (ex: I have 5 rows and if I delete 3 row, after delete, the checkbox checks 3 row again, which is row 4 before delete).
Any ideas?
Here is the code snippet.
this.checkedItems.forEach(selectedId => {
for (var i = 0; i 0) {
deleteLines({recordIds: this.checkedItems})
.then(result=>{
this.dispatchEvent(
new ShowToastEvent({
title: ‘Success’,
message: ‘Line Items deleted successfully’,
variant: ‘success’
})
);
return refreshApex(this.wiredLineItems);
})
.catch(error=>{
this.dispatchEvent(
new ShowToastEvent({
title: ‘Failure’,
message: error,
variant: ‘error’
})
);
})
}
Thanks you