149

I am using following code to get unique numbers:

let uniques = [ ...new Set([1, 2, 3, 1, 1]) ]; // [1, 2, 3]

However, typescript report following error: Type 'Set' is not an array type. I am not typescript ninja, could someone tell me what is wrong here?

Patrick Roberts
  • 49,224
  • 10
  • 102
  • 153
Eggy
  • 4,052
  • 7
  • 23
  • 39

7 Answers7

169

Update: With Typescript 2.3, you can now add "downlevelIteration": true to your tsconfig, and this will work while targeting ES5.

The downside of downlevelIteration is that TS will have to inject quite a bit of boilerplate when transpiling. The single line from the question transpiles with 21 lines of added boilerplate: (as of Typescript 2.6.1)

var __read = (this && this.__read) || function (o, n) {
    var m = typeof Symbol === "function" && o[Symbol.iterator];
    if (!m) return o;
    var i = m.call(o), r, ar = [], e;
    try {
        while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
    }
    catch (error) { e = { error: error }; }
    finally {
        try {
            if (r && !r.done && (m = i["return"])) m.call(i);
        }
        finally { if (e) throw e.error; }
    }
    return ar;
};
var __spread = (this && this.__spread) || function () {
    for (var ar = [], i = 0; i < arguments.length; i++) ar = ar.concat(__read(arguments[i]));
    return ar;
};
var uniques = __spread(new Set([1, 2, 3, 1, 1]));
console.log(uniques);

This boilerplate will be injected once per file that uses downlevel iteration, and this boilerplate can be reduced using the "importHelpers" option via the tsconfig. (See this blogpost on downlevel iteration and importHelpers)

Alternatively, if ES5 support doesn't matter for you, you can always just target "es6" in the first place, in which case the original code works without needing the "downlevelIteration" flag.


Original answer:

This seems to be a typescript ES6 transpilation quirk . The ... operator should work on anything that has an iterator property, (Accessed by obj[Symbol.iterator]) and Sets have that property.

To work around this, you can use Array.from to convert the set to an array first: ...Array.from(new Set([1, 2, 3, 1, 1])).

Retsam
  • 30,909
  • 11
  • 68
  • 90
  • @Restam : Does typescript provide polyfills for Array.from in IE if "target":"es5" in tsconfig.json ? – Rohit Rane Jul 28 '17 at 10:00
  • 1
    @jackOfAll No, Typescript doesn't do any polyfilling of prototypes for you. If you set "target": "es5" it should give you a compiler error if you attempt to use a method that needs to be polyfilled. – Retsam Jul 29 '17 at 14:52
  • 4
    @Restam great solution with `Array.from`. Most other people seem to just give up on this. thanks for a real solution! – rayepps Aug 14 '17 at 17:59
  • It's not a bug, they just don't support it for the `es5` target (see https://github.com/Microsoft/TypeScript/issues/4031). `Array.from` should work if you have `es2015` or higher (`es2017`, `esnext`) in your `lib` list in tsconfig. – Simon Hänisch Nov 27 '17 at 01:29
  • 1
    @SimonHänisch Thanks for the link: I've updated my answer, I no longer call it a "bug", but a "transpilation quirk", which is probably a more accurate term. I also added information about the downlevel iteration option from that link, which also solves the original issue. – Retsam Nov 27 '17 at 16:33
  • I think IE has a problem with Array.from(someSet) – Alejandro B. May 31 '18 at 20:19
  • @AlejandroB. Yeah, `Array.from` is an ES6 feature that's not supported in IE. `Set` is also an ES6 feature, so I didn't think that would be an issue, but apparently IE has partial `Set` support. In that case, I'd suggest the `downlevelIteration` solution. – Retsam Jun 01 '18 at 15:25
  • We had this problem with IE at work yesterday and fixed it doing `Array.from(theSet.values())` – Alejandro B. Jun 01 '18 at 18:56
130

You can also use Array.from method to convert the Set to Array

let uniques = Array.from(new Set([1, 2, 3, 1, 1])) ;
console.log(uniques);
Nate Getch
  • 1,479
  • 1
  • 11
  • 8
  • What's the point of spreading the array only to recapture it in a new array? – Robby Cornelissen Feb 13 '18 at 04:41
  • 1
    If not possible to target "es6", in tsconfig. And using Set with spread operator is required, how would you do it? – Nate Getch Feb 14 '18 at 17:58
  • 4
    The point is that if you use `Array.from()`, you no longer need the spread operator. It just adds overhead. `let uniques = Array.from(new Set([1, 2, 3, 1, 1]));` – Robby Cornelissen Feb 15 '18 at 08:14
  • 2
    @RobbyCornelissen the whole reason for above code is to make an array with unique values of original array – Reza Aug 18 '21 at 01:46
60

This is a missing feature. TypeScript only supports iterables on Arrays at the moment.

basarat
  • 261,912
  • 58
  • 460
  • 511
  • Thanks for clarification. I will use .filter() or something else to get job done. I also found few issues on github about this particular error. I will keep an eye on this in future releases. – Eggy Nov 02 '15 at 11:26
41

In Javascript:

[ ...new Set([1, 2, 3, 1, 1]) ]

In Typescript:

Array.from(new Set([1, 2, 3, 1, 1]))

In React State (setState):

setCart(Array.from(new Set([...cart, {title: 'Sample', price: 20}])));
Najathi
  • 2,529
  • 24
  • 23
20

You need to set "target": "es6", in your tsconfig.

phil294
  • 10,038
  • 8
  • 65
  • 98
11

Now, you can use Set in your Typescript setup (No need to target es6):

In you tsconfig.json, add this line:

{
  "compilerOptions": {
    /* Visit https://aka.ms/tsconfig.json to read more about this file */

    
    "downlevelIteration": true,                  /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
    
  },
  ...
}
Shivam Jha
  • 3,160
  • 3
  • 22
  • 36
4

To make it work, you either need "target": "ES6" (or higher) or "downlevelIteration": true in the compilerOptions of your tsconfig.json . This resolved my issue and working good or me.Hope it will help you also.

Mayur Saner
  • 444
  • 5
  • 10