3
votes
interface Props {
    data: string[] | string[][];
}

function Component({ data }: Props) {
    return data.map(v => v);
}

map() throws an error: This expression is not callable. Each member of the union type '((callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[]) | ((callbackfn: (value: string[], index: number, array: string[][]) => U, thisArg?: any) => U[])' has signatures, but none of those signatures are compatible with each other.(2349)

Why is that and how can I fix it?

Code in TypeScript Playground

2

2 Answers

1
votes

That's a limitation of TypeScript. If you have a union of two arrays:

string[] | number[]

you can't call any method on it. You need to disjoin the union first.

Solution one

interface Props {
    data: string[] | string[][];
}

function isString(arg: any): arg is string {
    return typeof arg === 'string'
}

function isStringArray(arg: any): arg is string[] {
    return Array.isArray(arg) && arg.every(isString);
}

function Component({ data }: Props) {
    if (isStringArray(data)) {
        data.map(str => str)
    } else {
        data.map(strArr => strArr);
    }
}

Solution two

Try a generic version.

interface Props<T> {
    data: T[];
}

function Component<T extends string | string[]>({ data }: Props<T>) {
    return data.map(v => v);
}
1
votes

The difficulty is that Array<T>.map has a union type, that is:

function foo({ data }: Props) {
  // x has the type:
  // (<U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[]) | 
  // (<U>(callbackfn: (value: string[], index: number, array: string[][]) => U, thisArg?: any) => U[])
  let x = data.map;
}

This is a union of the functions, not a function with the parameters unioned.

If the definition was something like this:


function otherMap<U>(
  data: string[] | string[][],
  callbackfn:
    (value: string | string[], index: number, array: string[] | string[][]) => U,
  thisArg ?: any
): U[] {
  // ...
}

You could use a type guard, or define a function that does exactly what you want instead of using Array.map.

The type guard option is as follows:


function bar({ data }: Props) : string[] | string[][] {
  if (isMultiDimArray(data)) {
    return data.map(z => z);
  }

  return data.map(z => z);
}

function isMultiDimArray<T>(data: T[] | T[][]): data is T[][] {
  if (data.length == 0) return false;

  var firstItem = data[0];
  return Array.isArray(firstItem);
}