0

I was introducing typescript to my current code. The size of the codebase is quite huge so started from the basic types. I have built custom data types to handle UI in a better way.

Following is the code for CustomArray which I have.

/**
 Author - Harkirat Saluja
 Git - https://bitbucket.org/salujaharkirat/
 **/

declare global {
  interface Array<T>{
    filterBy(o: T): Array<T>,
    sortAscBy(o: T): Array<T>,
    sortDescBy(o: T): Array<T>
  }
}


class CustomArray {
  static init (value = []) {
    if (!value) {
      value = [];
    }
    if (!(value instanceof Array)) {
      value = [value];
    }

    value.filterBy = function (property) {
      return !property ? this : this.filter(item => item[property]);
    };

    value.sortAscBy = function (property) {
      return !property ? this : this.sort((a, b) => a[property] - b[property]);
    };

    value.sortDescBy = function (property) {
      return !property ? this : this.sort((a, b) => b[property] - a[property]);
    };

    return arrayValue;
  }
}

export default CustomArray;

In production I am getting error TypeError: Cannot add property to a non extensible object.

As per the mozilla documentation, if we add Object.preventExtensions it should show me this error but I am not using that anywhere.

I was debugging it a bit and following code works fine:-

/**
 Author - Harkirat Saluja
 Git - https://bitbucket.org/salujaharkirat/
 **/

declare global {
  interface Array<T>{
    filterBy(o: T): Array<T>,
    sortAscBy(o: T): Array<T>,
    sortDescBy(o: T): Array<T>
  }
}


class CustomArray {
  static init (value = []) {
    if (!value) {
      value = [];
    }
    if (!(value instanceof Array)) {
      value = [value];
    }

    const arrayValue = [...value]; //Making a new copy works
    arrayValue.filterBy = function (property) {
      return !property ? this : this.filter(item => item[property]);
    };

    arrayValue.sortAscBy = function (property) {
      return !property ? this : this.sort((a, b) => a[property] - b[property]);
    };

    arrayValue.sortDescBy = function (property) {
      return !property ? this : this.sort((a, b) => b[property] - a[property]);
    };

    return arrayValue;
  }
}

export default CustomArray;

Though the error is fixed, it will really me if someone is able to help me understand why I am facing this issue?

Harkirat Saluja
  • 7,768
  • 5
  • 47
  • 73
  • Why aren't you using `CustomArray extends Array`? – adiga Dec 18 '19 at 06:42
  • Can u please share the code? It will really help me a lot :) @adiga – Harkirat Saluja Dec 18 '19 at 07:23
  • That's an unrelated suggestion. `CustomArray extends Array` will allow the instances `CustomArray` access all the functionalities of Array. That's not your issue here. Are you using react or something similar where the `value` parameter being passed is immutable? Like this: [Object is not extensible error when creating new attribute for array of objects](https://stackoverflow.com/questions/45798885) – adiga Dec 18 '19 at 07:31

1 Answers1

0

You are getting the error because you passed an array which is not-extensible. When you add a property value.filterBy, it will throw an error.

Instead of directly adding a property to every array, you could make CustomArray inherit from Array. This way, it will have all the functionalities of Array and the additional methods will be on CustomArray.prototype instead of directly on each array instances

Since you can get non-Array value as parameter, you could do [].concat(value) to always get an array.

const convert = v => [].concat(v)

console.log( convert(1) )
console.log( convert([1, 2]) )

Call the Array constructor using super and spreading the contents of the array.

class CustomArray extends Array {
  constructor(value = []) {
    super(...[].concat(value))
  }

  filterBy(property) {
    return !property ? this : this.filter(item => item[property]);
  }

  sortAscBy(property) {
    return !property ? this : this.sort((a, b) => a[property] - b[property]);
  }

  sortDescBy(property) {
    return !property ? this : this.sort((a, b) => b[property] - a[property]);
  }
}

const a1 = new CustomArray([{ age: 10 }, { age: 5 }])
console.log(a1.sortAscBy('age'))

const a2 = new CustomArray(["a", "bbb", "", "cc"])
console.log(JSON.stringify( a2 ))
console.log(JSON.stringify( a2.sortDescBy('length') ))
console.log(JSON.stringify( a2.filterBy('length') )) 
// ^ empty string is removed because item[property] returns 0 and 0 is falsy

const a3 = new CustomArray({ key: "value" }) // non-array item
console.log(a3)

const a4 = new CustomArray() // default empty array
console.log(a4)
adiga
  • 34,372
  • 9
  • 61
  • 83