79

Code speaks better than language, so:

['a', 'b', 'c'].reduce((accumulator, value) => accumulator.concat(value), []);

The code is very silly and returns a copied Array...

TS complains on concat's argument: TS2345: Argument of type 'string' is not assignable to parameter of type 'ConcatArray'.

Andru
  • 5,954
  • 3
  • 39
  • 56
millsp
  • 1,259
  • 1
  • 10
  • 23

7 Answers7

104

I believe this is because the type for [] is inferred to be never[], which is the type for an array that MUST be empty. You can use a type cast to address this:

['a', 'b', 'c'].reduce((accumulator, value) => accumulator.concat(value), [] as string[]);

Normally this wouldn't be much of a problem since TypeScript does a decent job at figuring out a better type to assign to an empty array based on what you do with it. However, since your example is 'silly' as you put it, TypeScript isn't able to make any inferences and leaves the type as never[].

Matt H
  • 1,795
  • 1
  • 7
  • 8
  • 5
    "Why does TypeScript infer the 'never' type?" "because the type for `[]` is inferred to be `never[]`". If that was the case then why does `const array = [];` infer `array` as type `any[]`? – Patrick Roberts Jan 09 '19 at 19:40
  • 1
    Normally TypeScript does a pretty good job at inferring the proper type for an empty array based on how it is used. However, it isn't able to in this example because it doesn't have any types in the parameters or return type of the callback, and it doesn't use the original ['a', 'b', 'c'] array for type inferencing. Doing something like `const array = [];` does indeed produce an `any[]` array. However, I think this is an explicit exception to avoid confusing people. If you do something like `[].filter(a => true);`, you will see that it infers the `never[]` type instead of the `any[]` type. – Matt H Jan 09 '19 at 19:47
  • 2
    Relevant: https://github.com/Microsoft/TypeScript/issues/18687 It's more complicated than I realized, but `[]` gets inferred as some kind of "widening `any[]`" type when contextually typed... apparently in a way that doesn't happen when being passed as the accumulator in `reduce()`. ‍♀️ – jcalz Jan 09 '19 at 19:51
  • 1
    @MattH Yes TS isn't able to determine the type of the array, you can either cast or pass the type as a template parameter to reduce then it works ! – millsp Jan 09 '19 at 20:40
  • Just found https://github.com/microsoft/TypeScript/issues/29795 which could be an authoritative source of the above info – jcalz Nov 22 '21 at 01:37
42

A better solution which avoids a type assertion (aka type cast) in two variants:

  1. Use string[] as the generic type parameter of the reduce method (thanks @depoulo for mentioning it):
['a', 'b', 'c'].reduce<string[]>((accumulator, value) => accumulator.concat(value), []);
  1. Type the accumulator value as string[] (and avoid a type cast on []):
['a', 'b', 'c'].reduce((accumulator: string[], value) => accumulator.concat(value), []);

Play with this solution in the typescript playground.

Notes:

  1. Type assertions (sometimes called type casts) should be avoided if you can because you're taking one type and transpose it onto something else. This can cause side-effects since you're manually taking control of coercing a variable into another type.

  2. This typescript error only occurs if the strictNullChecks option is set to true. The Typescript error disappears when disabling that option, but that is probably not what you want.

  3. I reference the entire error message I get with Typescript 3.9.2 here so that Google finds this thread for people who are searching for answers (because Typescript error messages sometimes change from version to version):

    No overload matches this call.
      Overload 1 of 2, '(...items: ConcatArray<never>[]): never[]', gave the following error.
     Argument of type 'string' is not assignable to parameter of type 'ConcatArray<never>'.
      Overload 2 of 2, '(...items: ConcatArray<never>[]): never[]', gave the following error.
     Argument of type 'string' is not assignable to parameter of type 'ConcatArray<never>'.(2769)
    
Andru
  • 5,954
  • 3
  • 39
  • 56
  • 6
    This should be the accepted answer. Slightly different solution, using generics: `['a', 'b', 'c'].reduce((accumulator, value) => accumulator.concat(value), []);` – depoulo Feb 16 '21 at 16:20
  • 1
    Thank you Andru for your valuable feedback. I too had the same issue; so googled and arrived at your correct answer. – Ammamon Nov 09 '21 at 03:30
9

You should use generics to address this.

['a', 'b', 'c'].reduce<string[]>((accumulator, value) => accumulator.concat(value), []);

This will set the type of the initial empty array, which in my opinion is the most correct solution.

rablentain
  • 6,641
  • 13
  • 50
  • 91
3

You can use Generic type to avoid this error.

Check my example of flatten function:

export const flatten = <T>(arr: T[]): T[] => arr.reduce((flat, toFlatten) =>
  (flat.concat(Array.isArray(toFlatten) ? flatten(toFlatten) : toFlatten)), [] as T[]);
bug5layer
  • 81
  • 1
  • 4
3

Two other ways to set the type that I like more than type casting (i.e [] as string) is:

  • <string[]>[]
  • Array<string>(0)
Emanuel Lindström
  • 1,607
  • 16
  • 25
1

I had the same problem with the reduce function. The previous answers are okay, but it's also pretty straightforward if you simply create and type the array before you pass it to the function as the second parameter.

const alphabet = ['a', 'b', 'c']
let accumulatorArray: string[] = []

const reduction = alphabet.reduce((acc, curr) => {
    acc.concat(curr)
}, accumulatorArray)
Kraken
  • 5,043
  • 3
  • 25
  • 46
-3

none of the above worked for me, even with altering the tsconfig.json file to "strict": false and was only able to avoid breaking the application with the following:

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
zach
  • 25
  • 2
  • 1
    This does not really answer the question. If you have a different question, you can ask it by clicking [Ask Question](https://stackoverflow.com/questions/ask). To get notified when this question gets new answers, you can [follow this question](https://meta.stackexchange.com/q/345661). Once you have enough [reputation](https://stackoverflow.com/help/whats-reputation), you can also [add a bounty](https://stackoverflow.com/help/privileges/set-bounties) to draw more attention to this question. - [From Review](/review/late-answers/30608007) – Andrew Halil Dec 20 '21 at 12:40