John Reilly
John Reilly

Reputation: 6269

Function returning a variable length generic array tuple

Consider the following:

function makeOutputArrayBasedOnInputArray<T>(arrayOfStuff: T[]) {
    return arrayOfStuff.map(item => ({ item }));
}

const x = makeOutputArrayBasedOnInputArray([1, 'two']);
const y = x[0] // { item: string | number; }
const z = x[1] // { item: string | number; }

What I'd love is instead for the types to be surfaced:

const y = x[0] // { item: number; }
const z = x[1] // { item: string; }

So imagine the doIt function was returning a tuple type which is entirely generic and of varying length; driven by the input to the function. So this would work too:

const x = makeOutputArrayBasedOnInputArray([7, 'nine', Date]);
const a = x[0] // { item: number; }
const b = x[1] // { item: string; }
const c = x[2] // { item: Date; }

Is this possible? Or have I reached the limits of infer and tuples?

See also: Variable length Array tuple in TypeScript? and Typescript, generic variadic factory function returning tuple

Update based on @A_blop's answer:

Hello and thanks for your answer! Whilst it does answer the question, it relies on the caller using as const. Without the as const, alas it doesn't work.

This may turn out to be key. Some context: the question behind the question is a PR I've raised to improve the type definitions of react-query: https://github.com/tannerlinsley/react-query/pull/1527/files

The change at present looks like this:

export function useQueries<
  TQueryFnData = unknown,
  TError = unknown,
  TData = TQueryFnData
>(
  queries: UseQueryOptions<TQueryFnData, TError, TData>[]
): UseQueryResult<TData, TError>[] {
// ...
}

And this is great but has the non-specific type inference problem. Ideally we'd like this behaviour:

const result = useQueries([
    { queryKey: key1, queryFn: () => 1 },
    { queryKey: key2, queryFn: () => 'two' },
])
// result[0].data => number | undefined
// result[1].data => string | undefined

But without requiring the caller specifying as const - which may not be possible?

Upvotes: 0

Views: 459

Answers (1)

A_blop
A_blop

Reputation: 862

You can add an explicit function return type consisting of a mapped tuple type:

function fn<T extends readonly any[]>(arr: T): {[K in keyof T]: {item: T[K]} } {
    return arr.map(item => ({ item })) as any;
}

const x = fn([1, 'two'] as const);
// x: readonly [{ item: 1; }, { item: "two"; }]
const y = x[0] // y: { item: 1; }
const z = x[1] // z: { item: "two"; }

Update: To narrow the type from the callee side, you can use variadic tuple types (TS 4.0):

function fn<T extends readonly any[]>(arr: [...T]): // <-- note [...T]
  {[K in keyof T]: {item: T[K]} } {...}

Playground code

Upvotes: 2

Related Questions