Search code examples
typescriptecmascript-5

"This expression is not callable. Each member of the union type... has signatures, but none of those signatures are compatible with each other." Why?


How to concat array of the union type in typescript?

When I try to use concat with the array of union type, I got this error:

This expression is not callable. Each member of the union type '{ (...items: ConcatArray<{...}>[]): { ... }[]; (...items: ({ ... } | ConcatArray<{ ...; }>)[]): { ...; }[]; } | { ...; }' has signatures, but none of those signatures are compatible with each other.

CodeSandbox: https://codesandbox.io/s/modern-breeze-qt9mb?file=/src/index.ts

Code example:

const arr1 = [
  { val1: 1, val2: 2, val3: 3 },
  { val1: 11, val2: 22, val3: 33 }
];

const arr2 = [
  { val1a: "1a", val2a: "2a", val3a: "3a" },
  { val1a: "11a", val2a: "22a", val3a: "33a" }
];

const arr3 = [
  { foo: "lfsfs", bar: "fabgg" },
  { foo: "l414g", bar: "fahrh" }
];

function getRandomArr() {
  if (Math.random() < 0.5) {
    return arr2;
  } else {
    return arr1;
  }
}
//error
const FinalArr = getRandomArr().concat(arr3);

Solution

  • You are following a red herring. Your problem has nothing to do with "arrays of the union type". The problem is that all of the arrays you are trying to concat have elements of different types, and concat requires that the arrays have elements of the same type T:

    Array<T> {
        concat(...items: ConcatArray<T>[]): T[];
        concat(...items: (T | ConcatArray<T>)[]): T[];
    }
    

    The above is from lib.es5.d.ts.

    You can quickly simplify the problem and remove the red herring by trying the following code and understanding the error messages:

    const a = arr1.concat(arr3)   // error
    const b = arr2.concat(arr3)   // error
    

    You original error message was also telling you this: "Each member of the union type... has signatures, but none of those signatures are compatible with each other."

    The only reason 'union type' is mentioned in the error message is because the message wants to tell you about the types that are incompatible, and the return type of getRandomArr() is a union type (by inference).

    solutions

    There are countless ways you can fix your code. Choose the one that makes the most sense from a type safety perspective. Here are some examples:

    if you care about the types

    If you care about the types of the various arrays involved, be explicit. Say you want your first three arrays to retain their specific types and their type safety, and you just want FinalArr to be able to handle elements of all three array element types. You could do this:

    type FinalElemType 
       = typeof arr1[number] | typeof arr2[number] | typeof arr3[number]
    
    function getRandomArr(): FinalElemType[] {
      ...
    }
    

    This will result in FinalArr also having the type FinalElemType[]. No errors, and you are being explicit about types and thus have type-safe code, assuming the types you are using are purposeful rather than hacks just to get errors to go away.

    if you don't care about the types / type safety

    type the arrays unknown[] or any[]

    If you don't care that the arrays have elements of different types, simply let Typescript know. For example, if you change the types of your first two arrays as follows, the concat won't complain. Inspect the types of all the elements and getRandomArr to understand what is going on.

    const arr1: unknown[] = [
      { val1: 1, val2: 2, val3: 3 },
      { val1: 11, val2: 22, val3: 33 }
    ];
    
    const arr2: unknown[] = [
      { val1a: "1a", val2a: "2a", val3a: "3a" },
      { val1a: "11a", val2a: "22a", val3a: "33a" }
    ];
    
    

    getRandomArr's return type will also become unknown[], and it will allow you to concat any other array to it.

    Or if you want arr1 thru arr3 to retain their type safety, and only don't care about the type of getRandomArr and FinalArr, you could do this:

    const FinalArr = (getRandomArr() as any[]).concat(arr3);
    

    Use spread syntax

    As @jsejcksn recommends, "Use spread syntax to combine the arrays", "See tsplay.dev/wOzdRW". This allows you to concat arrays with mixed element types:

    const FinalArr = [...getRandomArr(), ...arr3];