I'm new to LWCs development. I'm having an issue understanding the order of execution for the lightning-record-edit-form component when a Parent lwc has multiple Child lwcs. Each Child component contains a lightning-record-edit-form component as well so the save when submit is nested. When submitting the form and a validation errors, in some of the Child components (not all) the error is not propagated back to the Parent before the Parent components onsuccess event fires, so to the user it looks like both and error occurs and the transaction was successful.
The lightning-record-edit-form developer documentation says that when submitting the form events fire in this order: onclick, submit, then success or error. I'm clearyly missing something but don't know what it is. Although I have multiple components, the structure of each is the same, but each focuses on a specific section of my requirement.Any suggestions would be helpful. Thanks in advance.
Below is a mockup of my code structure. I only included one component because the structure of each is the same just different input fields:
ParentComponent.html
<template>
<template if:true={error}>
<c-error-panel error={error} onerrorclosed={handleError}></c-error-panel>
</template>
<div class="slds-media slds-media_center slds-grid slds-wrap slds-media_small slds-box">
<lightning-card title="Parent Component" icon-name="custom:custom13" style="width: 100%">
<lightning-layout-item padding="around-medium">
<div class="slds-media__body">
<lightning-record-edit-form record-id={recordId}
object-api-name={objectApiName}
onload={handleOnLoad} onsubmit={handleSubmit}
onerror={handleFormError}
onsuccess={handleSuccess}
density="auto">
<div class="slds-grid slds-gutters slds-wrap">
<!-- lightning-input-fields here -->
</div>
<div class="slds-docked-form-footer stickyFooter bottomfooter slds-large-size">
<lightning-button class="slds-var-m-around_xx-small" label="Cancel" onclick={handleReset}></lightning-button>
<lightning-button class="slds-var-m-around_xx-small" label="Save" name="save" type="submit" onclick={submitClick} variant="brand"></lightning-button>
</div>
</lightning-record-edit-form>
</div>
</lightning-layout-item>
</lightning-card>
</div>
<div class="slds-media__body">
<!--
Only load these components after the Parent is done loading and retrieving the child component ids
-->
<template if:true={finishedloading}>
<!--Conditional rendering of Subtasks-->
<template if:true={displayComponentA}>
<div class="slds-card">
<c-pc-component-a record-id={componentARecordId}
onload={handleOnLoad}
onsubmit={handleSubmit}
onerror={handleError}
onupdatecomponentaprogress={handleUpdateProgress}></c-pc-component-a>
</div>
</template>
<template if:true={displayComponentB}>
<div class="slds-card">
<c-pc-component-b record-id={componentBRecordId}
onload={handleOnLoad}
onsubmit={handleSubmit}
onerror={handleError}
onupdatecomponentbprogress={handleUpdateProgress}></c-pc-component-b>
</div>
</template>
...
<template if:true={displayComponentN}>
<div class="slds-card">
<c-pc-component-n record-id={componentNRecordId}
onload={handleOnLoad}
onsubmit={handleSubmit}
onerror={handleError}
onupdatecomponentnprogress={handleUpdateProgress}></c-pc-component-n>
</div>
</template>
</template>
</div>
</template>
ParentComponent.js
handleOnLoad(event){
//Set the flag for which Child Component to display
this.showSubtasks();
this.finishedloading = true;
}
handleSuccess(event) {
//Only show toast if no error from Child component(s)
if(((this.error == undefined) || (this.error == null))) {
const toastEvent = new ShowToastEvent({
title: "Platform Change Saved",
message: "The record was successfully saved.",
variant: "success"
});
this.dispatchEvent(toastEvent);
}
}
handleSubmit(event) {
//Stop the form from submitting
event.stopPropagation();
//This is also required to suppress the default submit process
event.preventDefault();
if(((this.error == undefined) || (this.error == null))) {
console.log('NO ERRORS OCCURRED calling submit on form - this.error = ' + this.error);
this.template.querySelector('lightning-record-edit-form').submit();
}
}
async submitClick() {
if(this.displayComponentA == true) {
await this.template.querySelector('c-pc-component-a').submitClick();
}
if(this.displayComponentB == true) {
await this.template.querySelector('c-pc-component-b').submitClick();
}
...
if(this.displayComponentN == true) {
await this.template.querySelector('c-pc-component-n').submitClick();
}
}
// handle errors when submit button press
handleFormError(event){
this.error = event.detail;
console.error("platformChange.handleFormError.error = " + this.error);
}
// handle errors from self
handleError(error){
this.error = error;
}
// handle error from child (subtask) components
/* This removes the component from the document, so can't use this...
errorCallback(error, stack){
this.error = error;
console.error("platformChange.errorCallback.error = " + this.error);
}
*/
ChildComponent.html
<template>
<div class="slds-media slds-media_center slds-grid slds-wrap slds-media_small slds-box">
<lightning-card title="ChildComponentA" icon-name="custom:custom67" style="width: 100%">
<lightning-layout-item padding="around-medium">
<div class="slds-media__body">
<lightning-record-edit-form record-id={recordId}
object-api-name={objectApiName}
onload={handleOnLoad}
onsubmit={handleSubmit}
onerror={handleError}
onsuccess={handleSuccess}
density="auto">
<!-- Lightning input fields here -->
</lightning-record-edit-form>
</div>
</lightning-layout-item>
</lightning-card>
</div>
</template>
ChildComponent.js
//declarators..
handleOnLoad(event) {
var record = event.detail.records;
this.setReadOnlyFlag();
this.updateProgress();
}
handleSuccess(event) {
this.isValidated = true;
this.error = undefined;
}
handleSubmit(event) {
//Tried this but don't think it's needed in the Child
//Stop the form from submitting
//event.stopPropagation();
//suppress the default submit process
//event.preventDefault();
this.error = undefined;
}
@api async submitClick(){
this.error = undefined;
//Timeline Status of the Parent
if(this.timelineStatus == 'Complete') {
//Stage of the Child
if(this.subtaskStage == 'N/A' || this.subtaskStage == 'Live') {
try{
await this.template.querySelector('lightning-record-edit-form').submit();
this.isValidated = true;
this.error = undefined;
}
catch (err) {
this.error = err;
this.isValidated = false;
//send the error back to the ParentComponent
const selectedEvent = new CustomEvent('error', {detail: this.error});
this.dispatchEvent(selectedEvent);
}
}
else {
this.isValidated = false;
this.error = "The Stage Must be N/A or Live when Timeline Status = Complete. \nPlease update and try again!";
//send the error back to the ParentComponent
const selectedEvent = new CustomEvent('error', {detail: this.error});
this.dispatchEvent(selectedEvent);
}
}
else {
try {
await this.template.querySelector('lightning-record-edit-form').submit();
this.isValidated = true;
this.error = undefined;
}
catch (err) {
this.isValidated = false;
this.error = err;
//send the error back to the ParentComponent
const selectedEvent = new CustomEvent('error', {detail: this.error});
this.dispatchEvent(selectedEvent);
}
}
}
handleError(event){
//An error occurred
this.isValidated = false;
// Error Handling
if (event.detail.output.errors != null) {
// Loop & Display Errors
for (let index = 0; index < event.detail.output.errors.length; index++) {
console.log('Displaying Errors');
event.detail.output.errors[index].message + '\n';
this.error += event.detail.output.errors[index].errorCode + '- ' + event.detail.output.errors[index].fieldLabel + ' ' + event.detail.output.errors[index].message + '\n';
}
console.log(JSON.stringify(this.error));
const selectedEvent = new CustomEvent('error', {detail: this.error});
this.dispatchEvent(selectedEvent);
}
if (event.detail.output.fieldErrors != null) {
console.log('Displaying Field Errors');
let fieldErrors = event.detail.output.fieldErrors;
let val = Object.values(fieldErrors);
this.error = Object.keys(fieldErrors) + ' ' + val[0][0]["message"];
console.log('pcSSO.handleError-FIELD ERRORS - ' + this.error);
//Send error back to ParentComponent...
const selectedEvent = new CustomEvent('error', {detail: this.error});
this.dispatchEvent(selectedEvent);
} else {
console.log('Displaying Generic Errors')
this.error = event.message;
console.log(JSON.stringify(this.error));
const selectedEvent = new CustomEvent('error', {detail: this.error});
this.dispatchEvent(selectedEvent);
}
}