2

Can anyone please explain to me why and how this might happen?

I have a typescript app with Zustand state management.

Somewhere inside the app I am updating certain elements by extracting them from the state and cloning them via a simple Object.Assign :

let elemToUpdate = Object.assign({},story?.content?.elementsData[nodeId]);

if (elemToUpdate) {

    if (elemToUpdate.title) elemToUpdate.title[editorLang] = newName;

    else elemToUpdate.title = {[editorLang]:newName} as TextDictionary;

    updateElement(nodeId,elemToUpdate);
}

Now the interesting part is: on my first try the update goes through without fail, but the next object I am trying to update fails with the following message:

Tree.tsx:39 Uncaught TypeError: Cannot assign to read only property 'en' of object '#<Object>'

I can't understand WHY the first one comes through, but the second gets blocked.

I know HOW to fix this: I need to do a deep clone. I just want to understand WHY.

2 Answers 2

4
+50

First, let's start from why some objects in your code are readonly. Based on what you described in the question, you use a Zustand state manager. Such managers traditionally wraps your stored data to readonly objects to prevent their manual mutation (expecting that you will change the state only via built-in mechanisms) to guarantee data stability. So, if the story?.content?.elementsData[nodeId] is the Zustand state object, it and all its nested objects are converted to readonly.

Second, let's define, which objects will be blocked. I see at least two objects here: elemToUpdate: { ..., title: { [lang]: string }} (that is elemToUpdate and its title). Both will be converted to readonly.

Third, you use Object.assign({}, ...) which creates a new object (new reference) and clones all the properties of the source object. Please note that this happens only for the first level of properties, it's not a deep clone. So, as the title is a reference to another object, it will be cloned as-is and in the new object it will still lead to the existing { [lang]: string } object. There are several ways to solve that:

  1. deep clone, as you mentioned
  2. manually clone title property, for instance {..., title: { ... elemToUpdate.title }} or via Object.assign

But I would not suggest you to mutate objects this way. Most probably your entire algorithm has some architectural issues in general.

Sign up to request clarification or add additional context in comments.

Comments

1

That is expected because in the first case you are not assigning a new value to the title property: you are only changing a value inside the title property. In the second case you are reassigning the whole value of the title property.

You can't change a readonly property. Let's understand it with a simple example:

Javascript: Only for example not related to problem

const user = { // a const is immutable
    name: 'John',
}
user.name = 'Pete'; // This works


const user = {
    name: 'John'
}
user = { name: 'Pete'} // This doesn't work

Typescript:

const user: Readonly<{
   a: {
        name: string
   }
}> = {
  a:{ name: 'John' }
}

user.a.name = 'Pete'; // This works
user.a = { name: 'John' } // Does not work

The same is happening in your example: Typescript does not check deep Readonly props. Please check here

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.