1

I'm trying to loop through an array of objects and add a property to each that meets certain criteria.

Here's the snippet:

onRecordEditFormLoad(event)
{    
    let tempFieldWrapperArray = this.fieldSetWrapper.fieldWrappers.map(element => element);
    //console.log('onRecordEditFormLoad running: tempFieldWrapperArray',JSON.stringify(tempFieldWrapperArray));

    let tempInputFieldArray = [];
    this.template.querySelectorAll('lightning-input-field').forEach(inputField => { 
        tempInputFieldArray.push({"fieldName":inputField.fieldName , "value":inputField.value});
    });
    console.log('onRecordEditFormLoad running: tempInputFieldArray',JSON.stringify(tempInputFieldArray));

    tempFieldWrapperArray.forEach(fieldWrapper => {
        let inputField = tempInputFieldArray.find(inputField => inputField.fieldName === fieldWrapper.fieldAPIName);
        console.log('onRecordEditFormLoad running: tempFieldWrapperArray: inputField',JSON.stringify(inputField)); 
        if(inputField){
            console.log('inputField.value',inputField.value);
            console.log('fieldWrapper',fieldWrapper);
            fieldWrapper.value = inputField.value;
        }
    });
    console.log('onRecordEditFormLoad running: tempFieldWrapperArray',JSON.stringify(tempFieldWrapperArray)); 
}

The code stops running where it adds the property: fieldWrapper.value = inputField.value;

The fieldWrapper object and inputField.value are valid and log. If I comment out fieldWrapper.value = inputField.value, the rest of the code runs.

3
  • What error do you get in the browser console when the offending code is executed?
    – Phil W
    Jul 1, 2020 at 19:46
  • No error. The code just stops running passed that line. Jul 1, 2020 at 19:48
  • (See sfdcfox's answer; basically results from wire and imperative class are frozen (using Object.freeze or similar), which means you need to clone the objects before you can modify the clones.)
    – Phil W
    Jul 1, 2020 at 19:53

2 Answers 2

5

Presuming that this came from a server call, you need to copy the objects entirely:

let tempFieldWrapperArray = this.fieldSetWrapper.fieldWrappers.map(element => ({...element}));

Or:

let tempFieldWrapperArray = this.fieldSetWrapper.fieldWrappers.map(element => Object.assign({},element));

This is because the entire response is locked behind a ReadOnlyMembrane that prevents modification so that the cache can't be corrupted by references.

By copying the elements using the spread operator (...), or Object.assign into a new object, you will remove the read-only properties of all top-level elements.

3
  • 1
    could you please update your answer to indicate that the objects obtained from the server are "frozen" and are therefore immutable. At that point the cloning then makes absolute sense. I would expect this is handled via use of Object.freeze.
    – Phil W
    Jul 1, 2020 at 19:57
  • @PhilW Good point.
    – sfdcfox
    Jul 1, 2020 at 20:03
  • Nice. How about adding a reference to github.com/salesforce/observable-membrane which includes a section on read-only membranes and appears to be part of the Salesforce infrastructure (note that Kevin Hill contributed).
    – Phil W
    Jul 1, 2020 at 20:08
2
let tempFieldWrapperArray = this.fieldSetWrapper.fieldWrappers.map(element => element);

Here, you are just assigning new memory location to array variable. But, Javascript allocates separate memory for main array and the elements inside it (if they are objects).

From documentation:

A non-primitive value (like an object or array) passed to a component is read-only. The component cannot change the content of the object or array. If the component tries to change the content, you see an error in the browser console. To mutate the data, make a shallow copy of the objects you want to mutate.

So, the object returned directly or in an array will be in immutable/frozen state. You will see debug logs in console when you Enable Debug Mode for Lightning Components

You need to return new copy for each object inside array as each one is immutable.

tempFieldWrapperArray = tempFieldWrapperArray.map(fieldWrapper => {
    fieldWrapper = {...fieldWrapper};
    -- code --
        fieldWrapper.value = inputField.value;
    
    return fieldWrapper;
});
6
  • This doesn't explain the issue clearly - if I copy/paste the two statements you have I CAN run them successfully.
    – Phil W
    Jul 1, 2020 at 19:56
  • you cannot if arr is api variable and being passed down to it Jul 1, 2020 at 19:57
  • I suggest you clarify you mean @api annotated. Yes, this is true again because the LWC framework ensures such values are frozen.
    – Phil W
    Jul 1, 2020 at 19:58
  • 2
    Can you add explanation as to why the objects in the array are immutable please.
    – Phil W
    Jul 1, 2020 at 20:01
  • 1
    Basically add something like "Objects received in an LWC either through an @api property or from a wire or imperative call to the server are immutable (likely using Object.freeze)."
    – Phil W
    Jul 1, 2020 at 20:02

You must log in to answer this question.

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