Lightning component.find("aura:id") returns an array consisting of one element

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.


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