11

I found the following excerpt in the documentation:

  • If the local ID is unique, find() returns the component.
  • If there are multiple components with the same local ID, find() returns an array of the components.
  • If there is no matching local ID, find() returns undefined.

In my case lightning component.find("aura:id") returns an array consisting of one element. How is that possible? Should not it have returned a single element instead?

I am sorry I can not provide the verifiable piece of code, because there is a lot going on there. I am just interesting, maybe someone else had the same problem and will be able to share the reason it happened to one and how you could guarantee to always get either an element or an array.

I will try to provide some code.

Here is what happens when the component loads:

onInit: function(component, event, helper) {
    const flowId = "a091r00001Z4GRyAAN";
    $A.createComponent(
        "zced:RPGFlowComponent",
        {
            "aura:id": "interactionsGraph",
            "flowId": flowId
        },
        helper.setUpperRightComponent(component)
    );
}

setUpperRightComponent: function(component) {
    return function(newComponent, status, errorMessage) {
        if (status === "SUCCESS") {
            const rightUpperPart = component.find("rightUpperComponent");
            const body = [];
            body.push(newComponent);
            rightUpperPart.set("v.body", body);
        }
        else if (status === "INCOMPLETE") {
            console.log("INCOMPLETE")
        }
        else if (status === "ERROR") {
            console.log("ERROR: " + errorMessage);
        }
    }
}

It just creates another component inside itself with aura:id being set to interactionsGraph.

And when I try the following code after the initialization:

const graph = component.find("interactionsGraph");

the graph is equal to a signle object. Here the component refers to the same component which called the onInit above.

But if at some point after initialization I run the following code:

updateRPGFlowGraph: function(component, event, helper) {
        event.stopPropagation();

        let refreshTweakerMenu = event.getParam("refreshTweakerMenu");
        if(refreshTweakerMenu !== false) {
            refreshTweakerMenu = true;
        }

        $A.createComponent(
            "zced:RPGFlowComponent",
            {
                "aura:id": "interactionsGraph",
                "flowId": event.getParam("flowId"),
                "selectedInteractionId": event.getParam("selectedInteractionId"),
                "refreshTweakerMenu": refreshTweakerMenu
            },
            helper.setUpperRightComponent(component)
        );
    }

then after running

const graph = component.find("interactionsGraph");

the graph will be equal to an array consisting of one object. The object is correct, in other words exactly the object I am expecting. But again the problem is that I would not like to create a crutch and check every time whether it is an array or an object.

UPDATE

Let us take a look at this part of code:

const rightUpperPart = component.find("rightUpperComponent");
const body = [];
body.push(newComponent);
rightUpperPart.set("v.body", body);

After executing the code we are definitely left with only one component which has the specified aura:id. And while we were executing the code there was no moment in time when we had two components with the same aura:id. As I understand it, we take a new component and put it in the place of the old one immediately.

So, now after running the code:

const graph = component.find("interactionsGraph");

I am expecting the graph to be a single object. Will the object property corresponding to the aura:id be preserved even if the component was deleted?

In other words if I had a component with aura:id being equal myCustomId that means that I had a map myCustomId : myComponent. Now I delete the myComponent, but still when I create a new component with the aura:id being equal myCustomId it will check that there is a key in map and so will create an array instead of putting a single object. Am I right?

6
  • 2
    I suspect that it has to do with Locker Service filtering out additional components that happened to match the ID, which is probably the only reason why this could happen. However, it shouldn't happen at all. If you can get a verifiable reproduction, it would be worth logging a bug over on Aura so they can look into this.
    – sfdcfox
    Commented Aug 3, 2018 at 16:56
  • @sfdcfox, I updated a question a little bit. Maybe it will shed at least some light on the root of the problem.
    – iloveseven
    Commented Aug 3, 2018 at 18:38
  • I have a theory. I'm going to try the theory and get back to you. Give me a few minutes.
    – sfdcfox
    Commented Aug 3, 2018 at 18:46
  • That was actually really easy to replicate...
    – sfdcfox
    Commented Aug 3, 2018 at 19:02
  • @sfdcfox, did you get the same problem as I am getting?
    – iloveseven
    Commented Aug 3, 2018 at 19:30

2 Answers 2

11

The reason only one item could be returned as the result of a find() call is that we keep a map of ID to component in our services.

"myId": "1:0"

If you register two components with the same ID, we convert it to an array.

"myId": ["1:0", "42:0"]

If you then destroy one of those components, it stays an array.

"myId": ["1:0"]

I do think there might be a bug on this, but I'm just here explaining what happened.

2
  • That exactly matched my hypothesis. Thanks for confirming it.
    – sfdcfox
    Commented Aug 3, 2018 at 21:56
  • I updated the question a little bit. Check it, please, if you will have an opportunity.
    – iloveseven
    Commented Aug 6, 2018 at 6:54
9

Okay, this seems to be because of how aura:id is cached and the Lightning life cycle. Here's a reproduction of the problem:

<aura:application >
    <aura:attribute name="dyn" type="Aura.Component[]" />
    {!v.dyn}
    <hr />
    <ui:button press="{!c.dynamicReset}" label="Reset" />
</aura:application>

({
    dynamicReset: function(component, event, helper) {
        console.log("Reset");
        $A.createComponents(
            [["ui:outputText",{"aura:id":"a1","value":"Hello World"}]],
            function(cmp, stat, err) {
                component.set("v.dyn", cmp);
                console.log("Immediate check");
                helper.showoutput(component);
                setTimeout($A.getCallback(function() {
                    console.log("Delayed Check");
                    helper.showoutput(component);
                }));
            }
        )
    }
})

({
    showoutput: function(component) {
        let res = component.find("a1");
        if(res) {
            if(res.length) {
                let output = "Array of "+res.length+" ";
                res.forEach(function(v,i) {
                    output = output + i + " Valid: " + v.isValid()+" ";
                });
                console.log(output);
            } else {
                console.log("One Component Found");
            }
        }
    }
})

This ultimately results in output that looks like this:

Reset
Immediate Check
One Component Found
Delayed Check
One Component Found
Reset
Immediate Check
Array of 2 0 Valid: true 1 Valid: true 
Delayed Check
Array of 1 0 Valid: true

Conclusion: It seems that however aura:id is being cached, it results in bugs by finding components that exist but are no longer part of the component hierarchy, and/or results in an array being created instead of immediate removal.

You can fix this by introducing a life cycle delay:

({
    dynamicReset: function(component, event, helper) {
        console.log("Reset");
        component.set("v.dyn", null);
        setTimeout($A.getCallback(function() {
        $A.createComponents(
            [["ui:outputText",{"aura:id":"a1","value":"Hello World"}]],
            function(cmp, stat, err) {
                component.set("v.dyn", cmp);
                console.log("Immediate check");
                helper.showoutput(component);
                setTimeout($A.getCallback(function() {
                    console.log("Delayed Check");
                    helper.showoutput(component);
                }));
            }
        )}));
    }
})

By setting it to null and allowing it to propagate, the one-element-array doesn't occur:

Reset
Immediate Check
One Component Found
Delayed Check
One Component Found
Reset
Immediate Check
One Component Found
Delayed Check
One Component Found
2
  • At least at first it seems a lot more difficult than just checking with the if statement. (x
    – iloveseven
    Commented Aug 3, 2018 at 19:33
  • Actually, the takeaway is you just need to move the logic to a helper function, if necessary, then wrap the call in a setTimeout.
    – sfdcfox
    Commented Aug 3, 2018 at 19:34

You must log in to answer this question.

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