47

Am not doing anything fancy, just trying to update a field on Contact in JS of LWC but getting this exception.

Uncaught TypeError: 'set' on proxy: trap returned falsish for property 'Name' throws at mydomain/auraFW/javascript/mhontaYdOya4Y_lBu7v9yg/aura_prod.js:2:27687

HTML Code:

<template>

    <template if:true={wiredContact}>

        {wiredContact.Name}

        <lightning-input value={wiredContact.Name} onchange={updateName}></lightning-input>
    </template>

</template>

JS:

import { LightningElement ,wire,track,api } from 'lwc';
import myContact from "@salesforce/apex/ContactController.fetchContact";

export default class Myrefreshapextest extends LightningElement {


 @track wiredContact;

 @wire (myContact)
        fetchedContact({error, data}){
            if(data){
                console.log(JSON.stringify(data));
                this.wiredContact = data;
            }else if (error){
                console.log(error);
            }
    }

    updateName (event){
        console.log(JSON.stringify(event.detail.value));
        console.log(JSON.stringify(this.wiredContact));
        this.wiredContact.Name = event.detail.value;
    }

}

Apex:

public class ContactController {

    @AuraEnabled(cacheable=true)
    public static Contact fetchContact(){
        return [SELECT Id,Name FROM COntact LIMIT  1];
    }
}

On top of my head, am not doing anything wrong, anyone has idea what's wrong with my code?

When I print console.log(JSON.stringify(this.wiredContact)); I get old values so am pretty sure it exists.

I tried with @track and @api, but same response. Can anyone shed some light?

6
  • Do you get same issue when you use the individual field names viz., FirstName?
    – Jayant Das
    Commented Apr 5, 2019 at 16:41
  • Same error Uncaught TypeError: 'set' on proxy: trap returned falsish for property 'FirstName' throws at its same error doesnt matter if its normal contact or personAccountContact. Commented Apr 5, 2019 at 16:43
  • I got the error (not exactly the same) but was able to replicate. Seems like you cannot set a value directly to the property instead recreate the JSON and then assign. E.g., this.wiredContact = ["Name:" + event.detail.value]; this worked. Still trying to figure out.
    – Jayant Das
    Commented Apr 5, 2019 at 16:57
  • @JayantDas It's a design feature. One that I'm pretty sure isn't documented.
    – sfdcfox
    Commented Apr 5, 2019 at 17:09
  • @sfdcfox I just saw the behavior you mentioned just by trying it out, and it worked, even though my format above is not JSON, but I was intending that.
    – Jayant Das
    Commented Apr 5, 2019 at 17:15

6 Answers 6

74

Cached items are set as read-only (because otherwise you could corrupt the cache). If you want a modifiable object, you need to clone it.

this.wiredContact = Object.assign({}, data);

Based on comments, you can also use the rest parameter syntax in LWC:

this.wiredContact = {...data};

If you don't care about some mobile browsers, and you're using Lightning Web Security instead of Locker Service, you can also use the structuredClone function, which can handle even recursive structures and other things that the JSON.parse(JSON.stringify(value)) trick doesn't support. You can check the status of structuredClone on Can I Use.

16
  • 6
    +1 for Object.assign(). Didn't know something like this existed.
    – Jayant Das
    Commented Apr 5, 2019 at 18:24
  • 1
    Thanks sfdcfox. You are truly a Javascript wizard. Commented Apr 5, 2019 at 18:30
  • 2
    @JayantDas In ES6, the syntactic sugar is: this.wiredContact = {...data};
    – tsalb
    Commented Apr 5, 2019 at 23:32
  • 26
    One thing to note is that if the returned object is a complex one (for example an object with nested objects) it seems the nested objects are not "cloned" since trying to modify them will result is the same immutable error. In such case, the Object.assign and {...data} doesn't seem to do the trick, only JSON.parse(JSON.stringify(data)) Commented Apr 10, 2019 at 15:04
  • 1
    @jonathanwiesel, thanks for the nested complex note, this seems to be an issue as shallow copy will not work on nested complex(yet) so have to use as you suggested JSON.parse(JSON.stringify(data))..
    – user614946
    Commented Jan 5, 2020 at 15:02
2

This is what I could find from the documentation for wired service and that seems to be the case here (emphasis mine).

The wire service provisions an immutable stream of data to the component

So it most likely seems that when trying to set the values directly using this.wiredContact.Name, because of it's read only property, the values are not getting set.

However if you try to create a new data and then assign it to this.wiredContact, it works:

this.wiredContact = "{Name:" + event.detail.value + "}";
4
  • This solution presumes only one field. If you have multiple fields, you must remember to encode all of them or lose data. The solution I presented avoids this problem by copying everything on initialization, leaving the variable free to be modified in all other function calls.
    – sfdcfox
    Commented Apr 5, 2019 at 18:20
  • Yeah, this was a more direct answer to the problem that Pranay had mentioned. I am not really good at JS, so didn't even know how to do so unless you mentioned.
    – Jayant Das
    Commented Apr 5, 2019 at 18:22
  • +1 thanks for linking documentation. This was freaking me out. Commented Apr 5, 2019 at 18:29
  • 2
    I started looking only when @sfdcfox mentioned it was possibly not :) But at least it is mentioned. Easy to miss!
    – Jayant Das
    Commented Apr 5, 2019 at 18:30
2

For nested Objects I use two recursive functions :

const setNestedKey = (obj, path, value) => {

    if (path.length === 1) {
        obj = {...obj, [path] : value}
        return obj
    }
        return setNestedKey(obj[path[0]], path.slice(1), value)
    }

const assignObject = (obj, path, value) => {

    let newObj = setNestedKey(obj, path, value);
    if (path.length === 1) {
        return newObj;
    } 
    path.pop();
    return assignObject(obj, path, newObj);

}
4
  • Seems to imply you have "path" stored as an array? Or maybe I'm missing something. Wondering if you can explain how you're creating and binding that variable to input components. Commented Nov 4, 2019 at 17:47
  • Yes, "path" must be an array. I've improved it : Commented Nov 5, 2019 at 18:40
  • 1
    Yes, "path" must be an array. You can add this line in the assignObject function : let keys = Array.isArray(path) ? path : path.split('.'); to manage a string or an array. In my LWC, I have to set nested property of complex objects. i.e, to set p3 of myObj = { p1: v1, { p2 : v2, {p3: v3}}} the usage is myObj = assign(myObj, 'p1.p2.p3', value); I've also improve the function to work with array property. Commented Nov 5, 2019 at 19:01
  • Sorry : myObj = { p1: { p2 : {p3: v3}}} Commented Nov 5, 2019 at 19:09
2

Cached items read only, you need to create a new deep copy of the json object

this.wiredContact = JSON.parse(JSON.stringify(data));
0

Another way to avoid getting this error is by updating the way you call your Apex method. So instead of using the @wire decorator, you can just explicitly call your Apex method within the connectedCallback() lifecycle hook and removing the (cacheable=true) annotation from your Apex controller method. So your updated files would look like this:

JS:

import { LightningElement, wire, track, api } from 'lwc';
import myContact from "@salesforce/apex/ContactController.fetchContact";

export default class Myrefreshapextest extends LightningElement {

     connectedCallback() {
        myContact()
            .then(data => {
                console.log(data);
            })
            .catch(error => {
                console.log(error);
            })
    }

    //More code...

}

APEX:

public class ContactController {

    @AuraEnabled
    public static Contact fetchContact(){
        return [SELECT Id,Name FROM COntact LIMIT  1];
    }
}
2
  • 3
    But then I won't be able to use wire and have access to cached data? Commented Aug 19, 2019 at 20:31
  • Yes, that is correct. Without those decorators and annotations, your data won't be cached Commented Aug 29, 2019 at 16:01
0

With arrays, you can use the map function and clone the object. An example customizing columns for use in a lightning datatable where data is wired return result.

const contacts = data.map((contact) => ({
    ...contact,
    OwnerName: contact.Owner.Name,
    Industry: contact.Account.Industry
}));
this.contacts = contacts;

Adding to this as I ran into this scenario. I wanted to update the a record in an array without calling refresh apex. This has worked consistently so far. There may be another way more succinct.

updateRecord(recordInput)
.then((result) => {
    // Clone data
    const data = [...this._data];
    this._data = [];
    const index = data.findIndex((row) => row.Id === result.id);
    // Clone row (object)
    const row = {...data[index]};
    // Set some values
    row.Industry = "Banking";
    row.AccountSource = "Partner";
    // Replace row
    data.splice(index, 1, row);
    // Set data
    this._data = data;
    this.dispatchEvent(
        new ShowToastEvent({
            title: "Success",
            message: "Account updated",
            variant: "success"
        })
    );
})
.catch((error) => {
    this.dispatchEvent(
        new ShowToastEvent({
            title: "Error Updating Account",
            message: reduceErrors(error).join(", "),
            variant: "error",
            mode: "sticky"
        })
    );
})

You must log in to answer this question.

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