Skip to content
Advertisement

Iterating through a an array executing a switch statement returns TypeError: Cannot assign to read only property ‘location’ of object ‘#’

I have a read only array that i copied to become a mutable array let mutableForecast = [...forecast] I am taking that new array and iterating through it with forEach so i can mutate the array. im trying to use some flow control with a switch statement, but I am getting TypeError: Cannot assign to read only property 'location' of object '#<Object>'

let mutableForecast = [...forecast]
mutableForecast.forEach((obj, i) => {
    switch (obj.location) {
        case obj.location === "BRITISH_COLUMBIA":
            obj.location = "BC"
            break;
        default:
            obj.location = "oother"
    }

})

Whats the issue here? I’ve look at this, this, this and some others but cannot find an answer.

This is what the forecast array looks like before i copied it enter image description here

Advertisement

Answer

It’s hard to be sure without knowing where forecast comes from, but I suspect the problem is that the elements of the array are not plain objects, but instances of a custom type that are defined as immutable. Your third link has the likely solution. The key is that you can’t convert an array of immutables into an array of mutables simply by using rest & spread in this way. You need to modify the mutability of each item in the array individually.

You probably need something like this:

let mutableForecast = [...forecast]
mutableForecast.forEach((obj, i) => {
    // make this element's location property mutable
    Object.defineProperty(obj, 'location', { writable: true })
    
    // calculate and set new value
    switch (obj.location) {
        case 'BRITISH_COLUMBIA':
            obj.location = 'BC'
            break;
        default:
            obj.location = 'other'
    }
})

This might also work, and I think it’s cleaner. You’d have to try it to be sure:

let mutableForecast = Array.from(forecast)
.map(forecastItem => ({
    ...forecastItem,
    location: getShortLocation(forecastItem.location)
}))

function getShortLocation( sourceLocation ) {
    switch (sourceLocation) {
        case 'BRITISH_COLUMBIA': return 'BC'
        default:                 return 'other'
    }
}

The core problem we’re working around is that whatever package gives you forecast, it clearly trafficks in some custom datatype, some of whose properties are defined as immutable. That fact doesn’t show up when you log the objects, and it isn’t changed when you convert an array-like container into an array.

That’s because [...forecast] doesn’t edit the items, it just copies them as-is from one data structure into another. Actually, be to precise, it copies references to those objects into a new array. If the original objects are weird things with locked properties, then your new array will consist of weird things with locked properties. If we want to change the value of that property on each element, we need to redefine the property before doing so.

Consider a case like this:

let myDog = {
    species: 'dog',
    name: 'Fido'
}
//> myDog { species: 'dog', name: 'Fido' }

We can create another object with the same properties like so:

let congruentAnimal = {
    ...myDog
}
//> congruentAnimal { species: 'dog', name: 'Fido' }

If the same property names occurs twice, the engine will only honor the last one:

let myDog = {
    species: 'cat',
    name: 'Fido',
    species: 'dog' // this will cause cat to be ignored
}
//> myDog { name: 'Fido', species: 'dog' }

So, we can override individual object properties while copying by re-declaring those properties last:

let anotherCongruentAnimal = {
    ...myDog,
    species: 'NEW DOG'
}
//> anotherCongruentAnimal { name: 'Fido', species: 'NEW DOG' }

That’s what is going on in that second snippet. Here’s an expanded version:

// create a real array whose elements are *references* to
// the objects in the array-like forecast
let arrayOfImmutableForecasts = Array.from(forecast)

// create another real array of new objects
// whose property names, values, and metadata are
// the same as the source objects
let arrayOfMutableForecasts = arrayOfImmutableForecasts.map(originalObject => { 
    let newObject = {
        // I think this will also preserve special rules like immutability
        ...originalObject, 
        
        // before we finalize the object, we declare a new simple property
        // the engine will _drop_ the implied prop declaration from above
        // and define the prop based on this simple declaration instead
        location: 'new value'
    }
    
    return newObject
})
User contributions licensed under: CC BY-SA
2 People found this is helpful
Advertisement