hsz
hsz

Reputation: 152216

Functions return types not resolved correctly when called in a loop

I've created a simple map with two functions that take a single 'foo: string' argument and implement a various functions that are directly returned:

import { mapValues } from 'lodash';

const fun1 = (foo: string) => (name: string, surname: string) => {};
const fun2 = (foo: string) => (age: number) => {};

interface Actions {
  fun1: typeof fun1;
  fun2: typeof fun2;
}

const actions: Actions = { fun1, fun2 };

const mappedActions = mapValues(actions, action => action('bar'));

mappedActions.fun1(1);

With mapValues, I'm looping through those functions and invoke them with some common parameter. What I'd expect is a map of the semi-called functions assigned in mappedActions map, but there is still a problem with the types.

fun1 is not resolved as a function that takes (name: string, surname: string) arguments, but still the top level one.

How should I compose that to make TypeScript resolve the types correctly?

enter image description here

Upvotes: 3

Views: 340

Answers (2)

user3535915
user3535915

Reputation: 26

I think I found a way to resolve types properly:

import { mapValues } from 'lodash';

const fun1 = (foo: string) => (name: string, surname: string) => {};
const fun2 = (foo: string) => (age: number) => {};

type FunctionDictionary = { [id: string]: ((...args: any[]) => any) };

interface Actions extends FunctionDictionary {
  fun1: typeof fun1;
  fun2: typeof fun2;
}

const actions: Actions = { fun1, fun2 };

type ReturnTypeDict<T> = T extends { [id: string]: ((...args: any[]) => any) } ? {
  [P in keyof T]: ReturnType<T[P]>;
} : never;

const map: (actions: Actions) =>  ReturnTypeDict<Actions> = actions => {
  return mapValues(actions, action => action('bar')) as ReturnTypeDict<Actions>;
}

const mappedActions = map(actions);

mappedActions.fun1(1);

The key here is the ReturnType conditional type. In order to use it you have to create your own conditional type:

type ReturnTypeDict<T> = T extends { [id: string]: ((...args: any[]) => any) } ? {
  [P in keyof T]: ReturnType<T[P]>;
} : never;

And now you have to explicitly say that your interface extends needed dictionary type:

type FunctionDictionary = { [id: string]: ((...args: any[]) => any) };

interface Actions extends FunctionDictionary {
  fun1: typeof fun1;
  fun2: typeof fun2;
}

Then you create a function that properly maps data:

const map: (actions: Actions) =>  ReturnTypeDict<Actions> = actions => {
  return mapValues(actions, action => action('bar')) as ReturnTypeDict<Actions>;
}

Upvotes: 1

Grzegorz Gajda
Grzegorz Gajda

Reputation: 2474

I copied your snippet to CodeSandbox and analyzed Lodash's function mapValues() and I think types for mapValues() not match your purpose.

mapValues<T extends object, TResult>(obj: T | null | undefined, callback: ObjectIterator<T, TResult>): { [P in keyof T]: TResult };

As you can see, function mapValues takes an object (T extends object) and for each key of this object, assign TResult which makes this:

mapValues<Actions, ((name: string, surname: string) => void) | ((age: number) => void)>(obj: Actions, callback: ObjectIterator<Actions, ((name: string, surname: string) => void) | ((age: number) => void)>): {

I tried to use lodash (especially lodash/fp) with TypeScript and I can say not everything works perfectly, also it's very hard to create universal typings for all possibilities.

Upvotes: 2

Related Questions