In my code I have a dispatch function that takes one parameter and returns another function depending on the parameter, something like this:
type Reason = 'create' | 'delete';
function dispatch(reason: Reason) {
switch(reason) {
case 'create':
return createInternal;
case 'delete':
return deleteInternal;
}
}
function createInternal(name: string) {
console.log(name);
}
function deleteInternal(code: number) {
console.log(code);
}
Now this initially worked, since my returned functions had the same signature. After adding another function with a different signature, I realised that this has an issue. The return type of the 'dispatch' function is a union of all possibly returned functions.
The following usage is now not possible anymore, since typescript can't infer the correct type of the returned function.
dispatch('create')('123'); // Argument of type 'string' is not assignable to parameter of type 'never'.
dispatch('delete')(123); // Argument of type 'number' is not assignable to parameter of type 'never'.
Is there a way to fix this, or alternatively implement a better dispatch function that doesn't have this issue?
Here's a link to a playground.
I have found half a solution with the help of generics. Now I can correctly call the dispatched function, but I get further errors inside the dispatch function that I don't understand.
type Reason = 'create' | 'delete';
type DispatchedFunction<T extends Reason> = T extends 'create' ? typeof createInternal : typeof deleteInternal;
function dispatch<R extends Reason>(reason: R): DispatchedFunction<R> {
switch(reason) {
case 'create':
// Type '(name: string) => void' is not assignable to type 'DispatchedFunction<R>'.
return createInternal;
case 'delete':
// Type '(code: number) => void' is not assignable to type 'DispatchedFunction<R>'.
return deleteInternal;
}
return () => {} // I had to add this so the function doesn't return undefined
}
You could create a constant lookup object and just index into that:
type Reason = 'create' | 'delete';
const DispatchFunctions = {
'create': createInternal,
'delete': deleteInternal,
} as const;
function dispatch<R extends Reason>(reason: R): typeof DispatchFunctions[R] {
return DispatchFunctions[reason];
}
function createInternal(name: string) {
console.log(name);
}
function deleteInternal(code: number) {
console.log(code);
}
dispatch('create')('123');
dispatch('delete')(123);
The same approach works when converting Reason
to an enum:
enum Reason {
CREATE = 'create',
DELETE = 'delete',
}
const DispatchFunctions = {
[Reason.CREATE]: createInternal,
[Reason.DELETE]: deleteInternal,
} as const;
function dispatch<R extends Reason>(reason: R): typeof DispatchFunctions[R] {
return DispatchFunctions[reason];
}
function createInternal(name: string) {
console.log(name);
}
function deleteInternal(code: number) {
console.log(code);
}
dispatch(Reason.CREATE)('123');
dispatch(Reason.DELETE)(123);