The setup

In simple terms, I am trying to call SOQL from a server-side controller and return a custom object record to the lightning component. The problem: This is failing in the lightning component's controller as it (products variable) is getting an undefined variable. I know the record isn't empty because I use System.debug before returning the record and see it's populated. Do objects need to be converted from Apex to JavaScript or something? Is there a better approach I should be using? Any suggestions will be helpful. Thanks.

The Code


<aura:component controller="QuickAddKBMaxController" implements="lightning:availableForFlowScreens" access="global">
    <aura:attribute type="KBMaxProduct[]" name="products" />
    <aura:attribute type="KBMaxProduct" name="product" />
    <aura:attribute type="KBMaxProduct.KBMaxProductField[]" name="fields" />
    <aura:attribute type="KBMaxProduct.KBMaxProductField" name="field" />
    <aura:attribute type="Integer" name="indexNum" default="1" />

    <lightning:card title="Add Products">
        <lightning:layout multipleRows="true">
            <lightning:layoutItem size="12" padding="around-small">
                <c:AutoComplete aura:id="kbmax-record" label="KBMax Product" objectApiName="KBMax_Product__c" idFieldApiName="Id" valueFieldApiName="Name" /> <!-- This is just a fancy auto-complete dropdown field whose list is populated with all KBMax_Product__c records with the value mapped to the ID. Replaceable with regular dropdown. -->
            <lightning:layoutItem size="12" padding="around-small">
                <lightning:button label="Add Product" variant="brand" onclick="{!c.getSelectedValue}" /> <!-- Once the Add Product button is clicked, run the action from the lightning controller. -->
        <!--Dynamic list-->
        <aura:iteration var="product" items="{!v.products}"> <!-- Products list is updated from the getSelectedValue action in the lightning controller. Below is basic fields to show that it works, but currently does not. -->
            <div class="slds-box slds-box_x-small" style="vertical-align: middle; padding: 5px;">
                <lightning:layout multipleRows="false" verticalAlign="true" >
                    <lightning:buttonIcon iconName="utility:delete" alternativeText="Delete" title="Delete" onclick="{!c.deleteProduct}" value="{!v.product.name}" />
                    <lightning:buttonIcon iconName="utility:copy" alternativeText="Duplicate" title="Duplicate" />
                    <lightning:layoutItem size="1" padding="around-small">
                        <div class="header-column">
                            <p class="field-title" title="Field 1">Product</p>
                    <!--<aura:iteration var="field" items="{!v.product.fields}">
                        <aura:if isTrue="{!v.field.visible}">
                            <lightning:layoutItem size="1" padding="around-small">
                                <div class="header-column">
                                    <p class="field-title">{!v.field.name}</p>
                    <lightning:layoutItem size="1" padding="around-small">
                        <lightning:input type="number" name="quantity" label="Quantity" value="{!v.product.quantity}" min="1" />


Lightning Controller

     * Get Selected Value from AutoComplete Component
     * @author Manish Choudhari
     * */
    getSelectedValue : function(component, event, helper) {
        //find autocomplete component using aura id
        const autoCompleteComponent = component.find("kbmax-record");
            //get selected option from auto complete component's selectedOption attribute
            const selectedOption = autoCompleteComponent.get("v.selectedOption"); //Get ID from auto-complete dropdown field.
            var action = component.get("c.getProductInfo"); //Run getProductInfo method from Apex Controller
            action.setParams({ productId : selectedOption }); //Pass the ID from the dropdown to the method.
            var products = component.get("v.products"); //Get current list of products from the Lightning page
            action.setCallback(this, function(response){
                var state = response.getState();
                if (state === "SUCCESS") {
                    var p = response.getReturnValue(); //Get returned value (should be custom object KBMaxProduct which is KBMax_Product__c data with a list of KBMax_Product_Field__c data. Essentially bundling all related info together instead of dealing with a list of Products and a list of ProductFields.
                    alert("From server: " + p.name); //This is undefined, when it should exist.
                    products.push(p); //Add returned product to the Lightning page product list.
                    //set selected value in component attribute
                    component.set("v.products", products);
                    debugger; //Can see p = {} in Chrome Dev Tools. That's it. In an alert, it says [Object Object].
                else if (state === "INCOMPLETE") { //Never reaches this.
                    // do something
                else if (state === "ERROR") { //Never reaches this.
                    var errors = response.getError();
                    if (errors) {
                        if (errors[0] && errors[0].message) {
                            console.log("Error message: " +
                    } else { //Never reaches this.
                        console.log("Unknown error");
            var indexNum = component.get("v.indexNum"); //Used for differentiating multiple of the same product. 
            component.set("v.indexNum", indexNum++);
    deleteProduct : function (component, event, helper) {
        let value = event.getSource().get("v.value");

        var values = component.get("v.products");
        let pos = values.indexOf(value);
        //set selected value in component attribute
        component.set("v.selectedValues", values);

Apex Controller

public class QuickAddKBMaxController {

    public static KBMaxProduct getProductInfo(Id productId) {
        KBMax_Product__c product = [SELECT Id, Name, ProductId__c FROM KBMax_Product__c WHERE Id=:productId];

        KBMaxProduct p = new KBMaxProduct(product.Name, product.ProductId__c, 1, getProductFieldsInfo(productId));
        System.debug(p.name); //Returns 'Applecrate'
        System.debug(p); //Returns object data 'KBMaxProduct:[fields=(fieldata), name=Applecrate, etc'
        return p;
    public static List<KBMax_Product_Field__c> getProductFieldsInfo(Id productId) {
        List<KBMax_Product_Field__c> products = [SELECT Name, FieldId__c, Required__c, Type__c, Value_List__c, Visible__c FROM KBMax_Product_Field__c WHERE KBMax_Product__c=:productId];
        return products;

Apex Product Class

public class KBMaxProduct {
    public decimal id;
    public string name;
    public integer quantity = 1;
    public List<KBMaxProductField> fields;

    public KBMaxProduct(string name, decimal id, integer quantity, List<KBMax_Product_Field__c> fields) {
                                                                   //Name, FieldId__c, Required__c, Type__c, Value_List__c, Visible__c
        this.name = name;
        this.id = id;
        this.quantity = quantity;

        List<KBMaxProductField> fieldList = new List<KBMaxProductField>();
        for(KBMax_Product_Field__c field: fields) {
            fieldList.add(new KBMaxProductField(field.Name, field.FieldId__c, field.Type__c, field.Value_List__c, null, field.Required__c, field.Visible__c));
        this.fields = fieldList;


    public class KBMaxProductField {
        public string name;
        public string id;
        public Object value;
        public string fieldType;
        public List<string> valueList;
        public boolean required;
        public boolean visible;

        public KBMaxProductField(string name, string id, string fieldType, String valueListText, Object value, boolean required, boolean visible) {
            this.name = name;
            this.id = id;
            this.fieldType = fieldType;
            List<string> valueListTemp = (valueListText != null ? valueListText.split('\\n') : new List<string>());
            this.value = value;
            this.required = required;
            this.visible = visible;

1 Answer 1


When using a Wrapper class, you need to use the @AuraEnabled annotation:

public class KBMaxProductField {
    @AuraEnabled public string name;
    @AuraEnabled public string id;
    @AuraEnabled public Object value;
    @AuraEnabled public string fieldType;
    @AuraEnabled public List<string> valueList;
    @AuraEnabled public boolean required;
    @AuraEnabled public boolean visible;

Also, make sure you check the case of your variables, if applicable. In Lightning/JavaScript, everything is cAsE-sEnSiTiVe.

  • For the @AuraEnabled here: this is because they used <aura:attribute type="KBMaxProduct" right? if they did a more generic type="Object" it'd work, correct?
    – stackasaur
    Apr 22, 2020 at 19:40
  • Thanks, sfdcfox! The properties show up in the debugger now, but the component still shows up empty ({!v.product.name} for example above the commented out iteration section). I made sure to match the case-sensitivity with all lowercase variables which matches the aura:attributes and the Apex class property names.
    – icebird76
    Apr 22, 2020 at 19:57
  • Figured it out. Iteration variables don't need to start with 'v.'. It's just {!product.name}.
    – icebird76
    Apr 22, 2020 at 20:10

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .