[SalesForce] Cannot assign to read only property ‘bEditing’ of object ‘#‘ error LWC

I'm getting the above error, when trying to edit a property value of an object inside list. Please refer below code for better understanding.

HTML:

<template>
    <div class="container">
        <div class="slds-grid slds-gutters">
            <div class="slds-col" for:each={lstQuotes} for:item="objQuote" for:index="pindex" key={objQuote.Id}>
                Quote : <span>{objQuote.sQuoteName}</span><br/>
                Opportunity : <span>{objQuote.sOpptName}</span><br/>
                <div>
                    <template for:each={objQuote.lstComments} for:item="objComment" for:index="index">
                        <div key={objComment.sRecordId}>
                            <span if:true={objComment.bEditing}>
                                <input type="text" value={objComment.sComment}/>
                            </span>
                            <span class="comment" if:false={bEditing}>{objComment.sComment}</span> &nbsp; &nbsp; 
                            <span class="createdDate">{objComment.sCreatedDate}</span> 
                            &nbsp; &nbsp; <span class="createdBy">{objComment.sCreatedBy}</span> &nbsp; &nbsp; 
                            <span if:true={objComment.bShowEdit}>
                                <a onclick={handleEdit} data-pindex={pindex} data-index={index}>Edit</a>
                            </span>
                        </div>
                        
                    </template>
                </div>
                <div>
                    <input type="button" name={objQuote.sQuoteId} value="Add Comment" onclick={showCommentBox} />
                    <input class="hide" data-id={objQuote.sQuoteId} data-index={index} value={objQuote.sComment} onkeyup={changeComment}/>
                    <input type="button" class="hide" data-index={index} data-id={objQuote.sQuoteId} value="Save" onclick={saveComment} />
                  </div>
            </div>
        </div>
    </div>
</template>

The error is thrown when I click on Edit anchor tag, resulting in handleEdit js method being called. If you look at the html, you'll understand that I am displaying the comment for respective quote dynamically inside a span using {objComment.sComment} and on click of edit, I'll be displaying the same comment value inside an input field to allow edits to the comment. I use a boolean variable bEditing to hide and show input/span comment.

Below is the JS for better understanding:

@track lstQuotes =[];
    @track lstQuotesTemp = [];
    @track lstComments = [];
    //sComment = '';
    @wire(QUOTE_DATA)
    quotesData({error, data}){
        if(data){
            console.log('data : ' ,data);
            this.lstQuotes = data;
            //Create copy of records
            this.lstQuotesTemp = this.lstQuotes.map( obj => ({
                ...obj
              })); 
        }
        else if(error){
            console.log('error : ',error);
        }
    } 

The above wire method gets data from back-end which is displayed inside the web component.

Now lstQuotesTemp holds a list of records, and under each record there is a list of comments, lstComments.

I created lstQuotesTemp simply because lstQuotes is read only and changes to it's records would result in error.

Now, let's see what handleEdit method does:

handleEdit(event){
        let parentIndex = event.target.dataset.pindex;
        let index = event.target.dataset.index;
        console.log('Comment Record : ' ,this.lstQuotesTemp[parentIndex].lstComments[index].bEditing);
        this.lstQuotesTemp[parentIndex].lstComments[index].bEditing = true;
    }

It simply finds the comment record using index to make it editable on click of Edit. However, it seems that the lstComments is still read only even after creation of a copy of it's parent list.

Can someone please suggest a way to fix this error?

I believe creating a copy of lstComments( which is a list on record level of lstQuotes) while creating a copy of lstQuotes -> lstQuotesTemp should resolve this issue. However, I'm not sure how I can achieve this?

To create a copy of lstQuotes I used below:

this.lstQuotesTemp = this.lstQuotes.map( obj => ({
                    ...obj
                  })); 

Best Answer

I was able to solve the above. The issue was that lstComments under lstQuotesTemp was read-only and thus had to create their copy. Below is what I did :

for(let i=0;i<this.lstQuotesTemp.length; i++){
                this.lstQuotesTemp[i].lstComments = this.lstQuotesTemp[i].lstComments.map( obj => ({
                    ...obj
                  })); 
            }

It worked as expected after creating a copy of lstQuotesTemp.lstComment.