157

I'm having array of objects where object looks like this (values change):

   {
     stats: {
        hp: 2,
        mp: 0,
        defence: 4,
        agility: 11,
        speed: 6,
        strength: 31
     }
   }

I want to sort them in descending order by speed doing:

  array.sort((a, b) => {
            return b.stats.speed - a.stats.speed
        })

However I'm getting this error and I can't really decipher whats going on:

TypeError: Cannot assign to read only property '2' of object '[object Array]'

What am I missing?

Edit: Array of object in redux store:

const enemyDefaultState = [
{
    name: 'European Boy1',
    stats: {
        hp: 2,
        mp: 0,
        defence: 4,
        agility: 11,
        speed: 6,
        strength: 31
    }
},
{
    name: 'European Boy2',
    stats: {
        hp: 2,
        mp: 0,
        defence: 4,
        agility: 4,
        speed: 2,
        strength: 31
    }
},
{
    name: 'European Boy3',
    stats: {
        hp: 2,
        mp: 0,
        defence: 4,
        agility: 7,
        speed: 7,
        strength: 31
    }
},

]

I import the array and assign it to the variable:

 let enemies = getState().enemy;
        if (enemies) {
            //sort by speed stat
            enemies.sort((a, b) => {
                return b.stats.speed - a.stats.speed
            })
        }
18
  • 3
    It doesn't make sense to sort object properties; the ordering is not really under your control. If you need things in a specific order, put them in an array. Commented Nov 21, 2018 at 20:33
  • 1
    Your code seems fine, the error probably come from other parts of your code? Commented Nov 21, 2018 at 20:34
  • I want to sort array of objects by their properties (meaning first elemnt in the array would be the one with the biggest stats.speed value) though, not objects themselves, I cant even imagine that would make sense Commented Nov 21, 2018 at 20:35
  • @MazMat I was re-reading your question. It's not really clear because what you posted is not complete (or even syntactically correct). Is it the case that you have an array of objects, and each object has one of those "stats" sub-objects? And you want to sort the objects by "speed" value? Commented Nov 21, 2018 at 20:37
  • 2
    Try evaluating with a 'use strict'; above the expression. Commented Nov 21, 2018 at 20:50

4 Answers 4

422

Because the array is frozen in strict mode, you'll need to copy the array before sorting it:

array = array.slice().sort((a, b) => b.stats.speed - a.stats.speed)
Sign up to request clarification or add additional context in comments.

7 Comments

And this solves it, thank you. Must read on the freeze because I often sorted my arrays and never encountered such issue
@MazMat it's possible one of your dependencies has some sort of development flag enabled that uses runtime enforcement of immutable data so that you can catch problem areas early where you're mutating objects provided by the framework.
This also solves the same issue using "immer" when storing objects in a reducer. You can also use a spread version [...array].sort()
WTF JS? Thanks for the hint, but this is just ridicules. I understand this in case of const, but why shouldn't this be possible on a let for example? If you change it to let, of course your IDE will tell you to change it to const due to no changes. Just javascript 🤦‍♂️
@Tob let and const have nothing to do with mutability. const only prevents you from rebinding a variable to another value via assignment or compound assignment, it does not prevent mutation. Similarly, let does not mean that a value is mutable, it only means that you are allowed to rebind the variable to another value. The two concepts are unrelated.
|
39

The array is frozen to prevent mutation of the redux state. You use react cloneElement(): https://reactjs.org/docs/react-api.html#cloneelement

[...enemies].sort((a, b) => {
                return b.stats.speed - a.stats.speed
            })

1 Comment

if using redux, this is an amazing catch and should be considered a great answer. thank you
31

The reason as Patrick stated is because the array is frozen. So any method of copying the array will work such as the one he suggests.

array = array.slice().sort((a, b) => b.stats.speed - a.stats.speed)

I just want to add that the reason the array is frozen in your case is because your using the array as props from the redux store and props in React are immutable hence your not being able to mutate the array.

Comments

19

To be clear, the issue is not purely that the array is frozen. Frozen arrays can be iterated over. As noted in ReactJS - sorting - TypeError: 0 is read only, the issue is that Array.sort sorts the array in-place, meaning it attempts to mutate the array. That's why you need to pass it a mutable copy of the array.

5 Comments

thanks for the clue, it is not apparent that it is being sorted in place
That's ... frightening. Next time I learn that map() modifies the original array, too?
@NotX no, map, forEach, filter, etc do not modify the source array
Yeah, I know. Just wanted to point out how unexpected that behavior for sort() is. I figure it some skeleton in the legacy closet.
It's mostly a question of older vs newer JS. JS must always be backwards compatible, so there are basically never breaking changes. Functions such as map were introduced in ES6 relatively recently (2015), while sort is an older function

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.