4

Prior to enabling LWS in Spring '22 we were able to successfully 'modify' objects returned from an Apex Call. After enabling LWS we cannot modify the data. Allow me to give an example.

Below is some 'pseudo code' which :

  1. Calls an Apex method

  2. The result of the call is a list of custom objects, each of which has two attributes : quantity and price

  3. After the result is returned, we loop through all the items and perform a simple calculation using the above mentioned attributes, and store the result on the item in an attribute called TotalPriceAmount.

  4. The updated line items are stored in a variable that is bound to the UI, so we can see all three values :)

         return getLineItems({id : recordId})
         .then(result => {
    
             result.forEach(oneLineItemVm => {
    
                 /* Embellish the line item with a TotalPriceAmount so we can show it on the UI :) */
    
                 let quantity = oneLineItemVm.Quantity;
                 let unitprice = oneLineItemVm.UnitPrice;
    
                 oneLineItemVm.TotalPriceAmount = unitprice * quantity;
    
             });
    
     /* Store the embellished on a @tracked attribute somewhere
     lineItems = result;
     ....
    

That 'pattern' works fine before enabling LWS. The line that sets the TotalPriceAmount has NO effect after enabling LWS.

It appears that data returned from Apex is now not able to be modified? Can anyone confirm that they have seen similar behavior?

Of course, this has made me question the original pattern, and perhaps I agree that we should NOT be attempting to modify the data. It's not hard to imagine unintended side effects of this way of coding.

But the question remains still. Does anyone else see this changed behavior?

as an aside .... If a clone (using whatever method you like (in our case _.deepClone) is made of the returned data, then behavior is as expected (we can see the TotalPriceAmount) on the item.

We'll most likely be adopting this (cloning any returned data if it needs to be embellished) as standard practice from now on.

Any thoughts would be appreciated. Just looking for some confirmation here on what I'm seeing.

Thanks

edit : in case it is not clear, the apex method (in my pseudo code example : getLineItems) is NOT a cacheable method. It would look like this in the real world

 @AuraEnabled
    public static List < LineItemVm > getLineItems(Id id) {

If this method was decorated with (cacheable=true) then I of course would not be even asking the question. Attempting to modify the result of such a method has NEVER worked, nor should it.

the api version of the Apex Class : 48 the api version of the LWC : 52

My question stands as is : Would someone be able to confirm for me (like a yes or no answer) whether the behavior I am describing can be reproduced.

2 Answers 2

5

Edit: This will be fixed in Spring 22, as confirmed by engineering. The original answer will remain below.


In Locker Service (LS), they use an ObservableMembrane with a readOnlyMembrane within it that protects cached values. This membrane immediately rejects any writes to the protected object. If you attempt to write to an existing property, you get an error that reads Cannot assign to read only property 'x' on object, while if you attempt to write a non-existent property, you get Cannot add property x, object is not extensible. However, if the object comes from a non-cacheable Apex method, it is not protected by this readOnlyMembrane, and you're freely allowed to set any value for any property.

In LWS, they use a WeakMap that pairs together "red values" and "blue values." I did a deep dive for this answer because of a bug in this mechanic once before. All you really need to know for this answer is that red values are "safe" and blue values are "unsafe." When you get a response from Apex, your copy of that data is a blue value, and the protected version stored in sanitized storage is a "red value."

The blue value is wrapped in a Proxy that protects the data in various ways. If the Apex method that returns the data is marked as cacheable, then any attempt to write an existing property gets the error set' on proxy: trap returned falsish for property 'x'. Attempting to write a non-existent property is allowed on the blue object. When the Apex method is not cacheable, this extra protection isn't applied, and you can write whatever you want to the blue value.

This part of the answer is mostly an educated guess from my previous experience, but I have not yet identified the exact mechanism that is causing this behavior as of yet. It appears that, for whatever reason, any writes to the response object, which is a blue value, do not end up getting copied to the red value. When this blue value is subsequently traded for a red value at some point in the future, you end up losing whatever modifications were made to the previous blue value Proxy.

You can see this appear in the code I wrote to try and diagnose this problem.

  getUncached() {
    return new Promise((resolve, reject) => {
      uncachedMethod()
        .then((result) => {
          this.uncached = result;
          result.checked = true;
          result.message = 'Other message'
          console.log("uncached result json", JSON.stringify(result));
          console.log("uncached result object", result);
          console.log("uncached property json", JSON.stringify(this.uncached));
          console.log("uncached property object", this.uncached);
          resolve();
        })
        .catch((err) => {
          this.uncachedError = err.message;
          reject(err);
        });
    });
  }

Output:

uncached result json {"message":"Other message","checked":true}
uncached result object {message: 'Hello, world'}
uncached property json {"message":"Other message","checked":true}
uncached property object {message: 'Hello, world'}

Here, JSON.stringify is easily able to read the blue value properties we set, but the variable itself triggers a red value response, and all we get back is the original response. I get similar outputs on an Apex method that is cached, assuming I do not try to modify message, which is what my methods are returning:

@AuraEnabled(cacheable=true)
public static Map<String, Object> cachedMethod() {
    return new Map<String, Object> {
        'message' => 'Hello, world'
    };
}
@AuraEnabled(cacheable=false)
public static Map<String, Object> uncachedMethod() {
    return new Map<String, Object> {
        'message' => 'Hello, world'
    };
}

I am going to try and diagnose the problem to try and understand the problem better, but I feel like this is almost certainly a bug. At the very least, when they start enabling this for all customers, it will cause problems for anyone that's taken advantage of modifying uncached Apex responses in Locker Service. I will be reporting this as a potential bug this weekend.

The workaround is to copy the values before attempting to modify them, as you've discovered. Hopefully we'll be able to get this resolved before this becomes a major issue. Uncached values should behave the same in both LS and LWS.

8
  • thanks sfdcfox. That is quite simply the most informative, clear, well thought out, detailed and helpful answer I have EVER received on this forum. (like ..... freakin' awesome!!!!) Thank you!!! My plan at this stage will be to make the (rather simple) change of creating a clone of any non cached apex results and then work with that data instead of the original result. Of course, there is the theoretical performance hit, but for us, it practically makes no difference (and it will actually work like it did before LWS, which is always nice!!) Jan 23 at 13:06
  • If the issue is actually identified as a bug, and is actually fixed at sometime in the future, we may rollback the 'clone' changes mentioned above. Thank you again for the research you have done on this........ Jan 23 at 13:06
  • @PeterByrne I suspect it is a bug, and that they'll remedy this sooner than later. I can't imagine you're the only one ever to utilize this behavior, and it could break major implementations. I'll let you know what I hear.
    – sfdcfox
    Jan 23 at 13:25
  • I suggest you also cross-post this issue in the Trailblazer Community Chatter Group here, as all issues related to LWS are closely monitored: trailhead.salesforce.com/fr/trailblazer-community/groups/… Jan 24 at 9:25
  • @FabienTaillon Thanks for the link. I lost it for a moment. I have reported this directly to Zachary Kampel, who has advised me that Engineering is looking in to it, but I'll also put it there for visibility.
    – sfdcfox
    Jan 24 at 9:50
1

The team is actively investigating this suite of issues with Lightning Web Security and looking at possible ways to allow the data modification behavior as experienced Lightning Locker in the past in a secure manner.

Would you mind sharing the code to getLineItems, for us to see how the APEX requests are made and how you are interacting with the response once it is received? It would greatly help verify our potential solutions.

Please feel free to scrub any potential information you don't want to share or if you want to share privately please let me know.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.