108

I am working on angular 8.

  1. I have a page that displays a table. The table displays data from an object array taskList which the component gets as an @Input().
  2. I have a sorting function on the columns of this table.
  3. I also have a delete option on each row. When I click on the delete option it makes api call to delete the row and then another call to fetch the tasklist array. This is the effect that for the same
  @Effect()
  DeleteTask$: Observable<Action> = this.actions$.pipe(
    ofType(importActions.DELETE_TASK),
    switchMap(params =>
      this.globalService
        .deleteTask(params)
        .mergeMap(deleteSuccess => {
          return from([
            new importActions.DeleteTaskSuccess(deleteSuccess),
            new importActions.LoadTaskList(),
          ]);
        })
        .catch((error, caught) => {
          return Observable.of(new GlobalError(error));
        }),
    ),
  );

My problem is that the sorting function works fine when I do on first page load. But if I delete a row and then fetch the tasklist post-delete, I get the following error:

ERROR Error: Uncaught (in promise): TypeError: Cannot assign to read only property '0' of object '[object Array]'
TypeError: Cannot assign to read only property '0' of object '[object Array]'

The as per the error message the following function in my code gives the error

  exchange(a, b) {
    const temp = this.taskList[a];
    this.taskList[a] = this.taskList[b]; //this line gives error
    this.taskList[b] = temp;
  }

This function is the part of a sorting code that uses the tasklist array and sorts it.
The flow being ngOnchanges(detects change is taskList array) calls --> this.taskChange('export_name', 'asc') based on some condition calls --> this. exchange(a, b)

Following is my ngOnchanges method

ngOnChanges(changes: SimpleChanges) {
    if (this.taskList !== null && this.taskList !== undefined) {
      this.taskChange('export_name', 'asc');
    }
  }

Following is the main sorting method

  async taskChange(value, taskOrder) {
    this.sortOrder = taskOrder;
    this.selectedValue = value;
    const expr = {
      asc: (a, b) => a > b,
      desc: (a, b) => a < b,
    };
    for (let i = 0; i < this.taskList.length; i++) {
      for (let j = i + 1; j < this.taskList.length; j++) {
        switch (value) {
          case 'export_name':
            if (
              expr[this.sortOrder](this.taskList[i].name, this.taskList[j].name)
            ) {
              this.exchange(i, j);
            }
            break;
          case 'file_type':
            let type1;
            let type2;
            type1 = this.exportType.transform(this.taskList[i].code, []);
            type2 = this.exportType.transform(this.taskList[j].code, []);
            if (expr[this.sortOrder](type1, type2)) {
              this.exchange(i, j);
            }
            break;
        }
      }
    }
  }

I am not sure what exactly is causing this error when the array changes the second time. I have tried a bunch of things but none of them worked. From what I found online this might be happening because I'm trying to mutate an array received as @Input. But the above code, which mutates the 'tasklist` array works on initial page load. And only stops working when the array changes. Could someone help me out?

1
  • that is not the correct paradigm for ngrx. Do sorting inside of your selector. Commented Nov 22, 2020 at 20:06

5 Answers 5

306

Try creating a copy of the array before trying to sort it. Like using a spread operator.

arrayForSort = [...this.taskList]

Then after sorting you could assign it back to the taskList field

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

4 Comments

thanks @Jan, this is because .sort() edits directly edits the variable. See: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
we need a pipe() able sort operator, which would not force us to spread the array. this works great, but it seems like such an anti-pattern.
As of today you can use the method Array.prototype.toSorted() if you need to sort immutably an array ( it returns a new sorted array ).
For Angular 14 and NGRX this also works.
96

For those coming to this issue with this error message using React/Redux, it might be that you're trying to mutate state directly which isn't allowed.

In my case I had this setup for getting state within a thunk (simplified):

import store from "./myStore";

const state = store.getState();
const getItems = state => state.user.items;
const items = getItems(state);
// ↓ this blew up as it was attempting to manipulate `state`
items.sort((a, b) => a.order - b.order);

This was fixed for me by:

import store from "./myStore";

const state = store.getState();
const getItems = state => state.user.items;
// ↓ in my case items is an array, so I create a new array by spreading state here
const items = [...getItems(state)];
// ↓ which means we're not manipulating state, but just our `items` array alone
items.sort((a, b) => a.order - b.order);

3 Comments

So we have to copy directly from getState()! Many thanks!
yes, you need to clone the array to create an entirely new array otherwise any changes will affect the base state object
how about taking a copy with arr.slice()
4

I ran into this exact error while working on a nextjs project. I put a findIndex on an array of objects, trying to add a new key-value pair to a specific object of the array when i got this error. so i just did this:

const arrayOfObjects = [...someOtherObj.originalArrayKey]
const index = arrayOfObjects.findIndex((obj)=>{
  // I had some conditions here
})
arrayOfObjects[index] = newValue

Correct

const arrayOfObjects = [...someOtherObj.originalArrayKey]

wrong

const arrayOfObjects = someOtherObj.originalArrayKey

Comments

1

For anyone trying to set a function attribute to be a sorted input param such as the following:

foo(original, originalConfig){
const deepClone = _.deepClone(original);
deepClone.config = originalConfig.sort((item1, item2) => {});

You wil still get this error. Instead, you need to spread the parameter before you sort it:

deepClone.config = [...originalConfig].sort((item1, item2) => {});

Comments

0

The array is frozen() here, we need to make a new copy of it, and then sort will work.

Approach 1

const arrayOfObjects = [...someOtherObj.originalArrayKey].sort((a, b) => a.order - b.order)

Approach 2

const arrayOfObjects = someOtherObj.originalArrayKey.slice().sort((a, b) => a.order - b.order)

Approach 3

const arrayOfObjects = someOtherObj.originalArrayKey.map(item => item).sort((a, b) => a.order - b.order)

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.