[SalesForce] afterRender threw an error in ‘lightning:select’ [Cannot read property ‘setAttribute’ of null]

I'm intermittently receiving the following uncaught error on my lightning component.

Error thrown by salesforce

I have a parent lightning component which is used as an input form. The parent component uses metadata, stored in Javascript, to build the input elements on the form. The inputs are multiple child custom lightning components, which are created using $A.createComponents.

Additionally, the child component create their specific input, e.g. lightning:select or lightning:input, dynamically using $A.createComponent on initialisation of the custom component.

Bizarrely the error shown below only happens intermittently, 1 in every 2 or 3 times it will show. Or it will show repeatedly for 5-6 times then not again for 3-4 loads of the component.

I've determined that whenever the error appears the logs show that the afterRender function is called more than once per child component.

Salesforce's documentation states that afterRender is only called once in the lifecycle but it is definitely being called multiple times here as I created a custom renderer on the child component to analyse the issue.

When the component loads without error then the logs show one afterRender call per child component, as expected.

I have reviewed the code on the parent component and the functions being used to dynamically create the child components are only being called once.

How can I either determine why the afterRender is being called multiple times and prevent it OR ensure that the property it is trying to work with is not null.

EDIT:

I determined the issue was happening not on the creation of the components using $A.createComponents but when adding the created components to the body (Aura.Component[]) of the parent.

To resolve it I did the following:

  1. Created a new attribute of type Object[] on the component.
  2. In the callback function of $A.createComponents, instead of setting v.body of the parent component with the newly created components, add the components to the new attribute created in the previous step.
  3. Wrap the function that contains $A.createComponents in a promise.
  4. In the callback from the promise – in the 'then' – get the attribute from step 1 and assign its value to the v.body on the parent component where you want your newly created components to render.

Best Answer

I can confirm that suggested solution works. In my case, I wasn't assigning to v.body but an attribute which I was using in the body of the component itself.

I have simplified this by simply returning the new instance of the component via the resolve function and setting it to the desired attribute in the then callback.

Before:

    $A.createComponents([
        ['ns:myComponent', {options: options}]
    ],
    function (results, status) {
        component.set('v.output', results[0]);
    }

After:

new Promise((resolve, reject) => {
    $A.createComponents([
            ['ns:myComponent', {options: options}]
        ],
        function (results, status) {
            resolve(results[0]);
        }
    );
}).then(function(newComponent) {
    component.set('v.output', newComponent);
}).catch(console.error);