140

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
            })
        }
MazMat
  • 1,904
  • 3
  • 12
  • 24
  • 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. – Pointy Nov 21 '18 at 20:33
  • 1
    Your code seems fine, the error probably come from other parts of your code? – William Chong Nov 21 '18 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 – MazMat Nov 21 '18 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? – Pointy Nov 21 '18 at 20:37
  • Based on the error it sounds like the array has been [frozen](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze). – Patrick Roberts Nov 21 '18 at 20:39
  • @PatrickRoberts agreed; I tried a quick experiment to make an array slot be `writable: false` and that *works* but it does not trigger an exception. *edit* wait except freezing the object *also* does not cause an exception. – Pointy Nov 21 '18 at 20:40
  • That is correct @Pointy – MazMat Nov 21 '18 at 20:40
  • Well it's not clear from what I know about your code how you'd get an exception like that. Where does the array come from? – Pointy Nov 21 '18 at 20:41
  • Can we see the entire code where you're assigning the object to the variable that's being sorted? – Nunchy Nov 21 '18 at 20:41
  • Updated, is it enough? – MazMat Nov 21 '18 at 20:44
  • @Pointy I said the array, not the object. The _array_ is frozen, e.g. `Object.freeze(Array.from('gfedcba')).sort()` – Patrick Roberts Nov 21 '18 at 20:44
  • @PatrickRoberts yes I understood that; try it in your own console. Freezing the array itself does prevent assignment to any slot of the array, but no exception is thrown. (Sorry; I meant "freezing the array".) – Pointy Nov 21 '18 at 20:45
  • @Pointy [you were saying?](https://i.imgur.com/X4kQNvC.png) – Patrick Roberts Nov 21 '18 at 20:47
  • Interesting; Firefox throws no exception as far as I can tell. Like, if I make an array and freeze it and then immediately do `array[0] = "hello world";` the assignment does not happen but the browser throws no exception. – Pointy Nov 21 '18 at 20:48
  • 2
    Try evaluating with a `'use strict';` above the expression. – Patrick Roberts Nov 21 '18 at 20:50
  • ah that's probably it! *edit* yes, well that's a mystery solved :) Thanks – Pointy Nov 21 '18 at 20:50
  • For the record, [Firefox does the same thing as Chrome for me](https://i.imgur.com/gRf27FE.png). – Patrick Roberts Nov 21 '18 at 20:51
  • @MazMat so in the end it looks like Redux maybe freezes those objects for its own reasons? In any case that's what causes that error: the array itself is flagged as frozen and so slots of the array cannot be reassigned. – Pointy Nov 21 '18 at 20:52

4 Answers4

391

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)
Patrick Roberts
  • 49,224
  • 10
  • 102
  • 153
  • And this solves it, thank you. Must read on the `freeze` because I often sorted my arrays and never encountered such issue – MazMat Nov 21 '18 at 20:59
  • @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. – Patrick Roberts Nov 22 '18 at 00:43
  • 25
    This also solves the same issue using "immer" when storing objects in a reducer. You can also use a spread version `[...array].sort()` – Mark Hkr Apr 15 '20 at 20:33
  • 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 Jan 27 '22 at 08:25
  • 4
    @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. – Patrick Roberts Apr 03 '22 at 23:07
  • @PatrickRoberts I’m aware of it, but I don’t like it. In my opinion js should go in the direction, that a const object stays frozen. Currently the only way of prevent issues with mutability is to write immutable style code, with a lot of spread operations or assign. I only wish for write on copy. But I’m aware we will never get value semantics in js. – Tob Apr 05 '22 at 06:00
  • But why can't I call `sort()` a frozen array? I mean, I'm not actually modifying it, but creating a new one, right? Edit: `sort()` actually sorts the array itself, too. Tbh. I'm a bit shocked by that. :D – NotX Dec 19 '22 at 14:45
33

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
            })
slash
  • 347
  • 3
  • 2
32

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.

Nick Friedman
  • 461
  • 5
  • 8
17

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.

Matthew Herbst
  • 29,477
  • 23
  • 85
  • 128
  • thanks for the clue, it is not apparent that it is being sorted in place – JFFIGK Sep 03 '20 at 18:56
  • That's ... frightening. Next time I learn that `map()` modifies the original array, too? – NotX Dec 19 '22 at 14:52
  • 1
    @NotX no, `map`, `forEach`, `filter`, etc do not modify the source array – Matthew Herbst Dec 19 '22 at 18:35
  • Yeah, I know. Just wanted to point out how unexpected that behavior for `sort()` is. I figure it some skeleton in the legacy closet. – NotX Dec 19 '22 at 18:37
  • 1
    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 – Matthew Herbst Dec 19 '22 at 18:45