Fixing "Cannot assign to read only property" Error in JavaScript Arrays
When working with arrays in JavaScript/TypeScript, you may encounter the error TypeError: Cannot assign to read only property '0' of object '[object Array]'. This error occurs when attempting to modify an array that has been marked as read-only or frozen.
Problem Overview
The error typically happens in these scenarios:
- Immutable state management: When working with state management libraries like Redux or NgRx where state is immutable
- Input properties: When trying to mutate arrays received as
@Input()properties in Angular - Frozen objects: When working with objects that have been explicitly frozen using
Object.freeze()
In the original problem, the issue occurred when trying to sort an array received as an @Input() in an Angular component after the array was refreshed from an API call.
Root Cause
JavaScript arrays are normally mutable, but when an array is:
- Received as an input property in Angular
- Part of an immutable state (Redux, NgRx, etc.)
- Explicitly frozen with
Object.freeze()
It becomes read-only, and any attempt to modify it directly will throw the error.
Solutions
1. Create a Copy Before Modification
The most straightforward solution is to create a copy of the array before attempting to modify it:
// Using spread operator (recommended)
const mutableArray = [...originalArray];
// Using slice()
const mutableArray = originalArray.slice();
// Using Array.from()
const mutableArray = Array.from(originalArray);2. For Angular Input Properties
When working with Angular components, avoid mutating input arrays directly:
@Component({
// Component configuration
})
export class MyComponent {
private _taskList: any[] = [];
@Input()
set taskList(value: any[]) {
// Create a copy when the input changes
this._taskList = [...value];
}
get taskList(): any[] {
return this._taskList;
}
exchange(a: number, b: number) {
// Now we can safely mutate our internal copy
const temp = this._taskList[a];
this._taskList[a] = this._taskList[b];
this._taskList[b] = temp;
}
}3. For Redux/State Management
When working with immutable state:
// Instead of this (will cause error):
const items = getItems(state);
items.sort((a, b) => a.order - b.order);
// Do this (create a copy first):
const items = [...getItems(state)];
items.sort((a, b) => a.order - b.order);4. Complete Sorting Solution
Here's how to rewrite the sorting function to avoid the error:
async taskChange(value: string, taskOrder: string) {
this.sortOrder = taskOrder;
this.selectedValue = value;
// Create a working copy of the array
const workingArray = [...this.taskList];
const expr = {
asc: (a, b) => a > b,
desc: (a, b) => a < b,
};
for (let i = 0; i < workingArray.length; i++) {
for (let j = i + 1; j < workingArray.length; j++) {
switch (value) {
case 'export_name':
if (expr[this.sortOrder](workingArray[i].name, workingArray[j].name)) {
// Exchange elements in the working array
const temp = workingArray[i];
workingArray[i] = workingArray[j];
workingArray[j] = temp;
}
break;
case 'file_type':
let type1 = this.exportType.transform(workingArray[i].code, []);
let type2 = this.exportType.transform(workingArray[j].code, []);
if (expr[this.sortOrder](type1, type2)) {
const temp = workingArray[i];
workingArray[i] = workingArray[j];
workingArray[j] = temp;
}
break;
}
}
}
// Assign the sorted array back (if needed)
this.taskList = workingArray;
}WARNING
Note: The above implementation uses a bubble sort algorithm, which is inefficient for large arrays (O(n²) time complexity). For better performance, consider using JavaScript's built-in Array.prototype.sort() method.
5. Modern Approach with Built-in Sort
For better performance, use the native array sort method:
taskChange(value: string, taskOrder: string) {
const workingArray = [...this.taskList];
workingArray.sort((a, b) => {
if (value === 'export_name') {
const comparison = a.name.localeCompare(b.name);
return taskOrder === 'asc' ? comparison : -comparison;
} else if (value === 'file_type') {
const typeA = this.exportType.transform(a.code, []);
const typeB = this.exportType.transform(b.code, []);
const comparison = typeA.localeCompare(typeB);
return taskOrder === 'asc' ? comparison : -comparison;
}
return 0;
});
this.taskList = workingArray;
}Preventative Measures
- Always assume inputs are immutable: Treat all input arrays as potentially read-only
- Use TypeScript: It can help catch some immutability issues at compile time
- Document immutable data: Clearly document which arrays should not be mutated
- Use immutable data structures: Consider libraries like Immutable.js for complex state management
Common Pitfalls
DANGER
These approaches will still cause the error:
// Direct assignment without copying
const arr = originalArray;
// Methods that modify arrays in place
originalArray.sort();
originalArray.reverse();
originalArray.push();
originalArray.pop();Conclusion
The "Cannot assign to read only property" error occurs when attempting to mutate an array that has been marked as immutable. The solution is to always create a copy of the array before performing any mutations. Use the spread operator [...array], array.slice(), or Array.from(array) to create mutable copies of potentially immutable arrays.
This approach ensures your code works correctly with:
- Angular input properties
- Redux/state management systems
- Any other scenario where arrays might be frozen or made read-only