Retrieve functions with varying signatures from another function with precise typing requirements

My code includes a dispatch function that takes a parameter and then returns a different function based on the parameter value. Here is a simplified version:

type Choice = 'start' | 'end';

function dispatch(choice: Choice) {
    switch(choice) {
        case 'start':
        return startProcess;
        case 'end':
        return endProcess;
    }
}

function startProcess(name: string) {
    console.log(name);
}

function endProcess(code: number) {
    console.log(code);
}

Initially, everything worked fine because all returned functions had the same signature. However, adding another function with a different signature caused an issue. The problem lies in TypeScript not being able to infer the correct type of the returned function from the 'dispatch' function.

Now, the following usage is no longer possible due to TypeScript's inability to understand the return type:

dispatch('start')('123'); // Argument of type 'string' is not assignable to parameter of type 'never'.
dispatch('end')(123); // Argument of type 'number' is not assignable to parameter of type 'never'.

Is there a way to resolve this issue or perhaps implement a more robust dispatch function that doesn't face such difficulties?

Here's a link to a playground.

Edit

I have found a partial solution using generics. This allows me to correctly call the dispatched function, but I encounter additional errors within the dispatch function which I am struggling to comprehend.

Playground

type Choice = 'start' | 'end';
type DispatchedFunction<T extends Choice> = T extends 'start' ? typeof startProcess : typeof endProcess;

function dispatch<R extends Choice>(choice: R): DispatchedFunction<R> {
    switch(choice) {
        case 'start':
        // Type '(name: string) => void' is not assignable to type 'DispatchedFunction<R>'.
        return startProcess;
        case 'end':
        // Type '(code: number) => void' is not assignable to type 'DispatchedFunction<R>'.
        return endProcess;
    }
    return () => {} // Added to prevent function from returning undefined
}

Answer №1

To simplify the process, you can utilize a constant lookup object and access it through indexing:

type Signal = 'start' | 'finish';

const SignalActions = {
    'start': startProcess,
    'finish': finishProcess,
} as const;

function trigger<S extends Signal>(signal: S): typeof SignalActions[S] {
    return SignalActions[signal];
}

function startProcess(taskName: string) {
    console.log(taskName);
}

function finishProcess(taskId: number) {
    console.log(taskId);
}

trigger('start')('New Task');
trigger('finish')(456);

This same pattern applies when converting Signal to an enum:

enum Signal {
    START = 'start',
    FINISH = 'finish',
}

const SignalActions = {
    [Signal.START]: startProcess,
    [Signal.FINISH]: finishProcess,
} as const;

function trigger<S extends Signal>(signal: S): typeof SignalActions[S] {
    return SignalActions[signal];
}

function startProcess(taskName: string) {
    console.log(taskName);
}

function finishProcess(taskId: number) {
    console.log(taskId);
}

trigger(Signal.START)('New Task');
trigger(Signal.FINISH)(456);

Answer №2

In my opinion, TypeScript appears to be slightly confused in this scenario, requiring an explicit casting of the return to that specific generic type.

        switch (operation) {
            case 'create':
                return createInternal as DispatchedFunction<R>;
            case 'delete':
                return deleteInternal as DispatchedFunction<R>;
        }

Answer №3

Here is a possible implementation:

type Action = 'add' | 'remove';

function handleAction(action: Action):(args:any)=> any {
    switch(action) {
        case 'add':
            return performAddition;
        case 'remove':
            return performRemoval;
    }
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the syntax for creating a zip function in TypeScript?

If I am looking to create a zip function: function zip(arrays){ // assume more than 1 array is given and all arrays // share the same length const len = arrays[0].length; const toReturn = new Array(len); for (let i = 0; i < len; i+ ...

Clicking a button in React requires two clicks to update a boolean state by triggering the onClick event

I have a React functional component with input fields, a button, and a tooltip. The tooltip is initially disabled and should only be enabled and visible when the button is clicked and the input fields contain invalid values. The issue I'm facing is t ...

Enhance your AJAX calls with jQuery by confidently specifying the data type of successful responses using TypeScript

In our development process, we implement TypeScript for type hinting in our JavaScript code. Type hinting is utilized for Ajax calls as well to define the response data format within the success callback. This exemplifies how it could be structured: inter ...

$(...).parentElement is not a function - Troubleshooting a Problem with WebDriver IO and TypeScript

Alright, the objective is simple. I need to ascend from the root to obtain its parent element. Following the webdriver documentation, it should resemble something like this: it('should retrieve the class from the parent element', async () => { ...

Steps for converting an observable http request into a promise request

Here is a link to my service file: https://i.sstatic.net/8dsMx.png This is the TypeScript file for my components: https://i.sstatic.net/of6sx.png And these are the response models: https://i.sstatic.net/U8RUQ.png https://i.sstatic.net/7baTj.png I am curre ...

Converting React useState to a JSON object data type

I imported a JSON data file using the following code: import data from "../data.json"; The contents of the file are as follows: [ { "name": "Emery", }, { "name": "Astrid", }, { " ...

Attempting to simulate the behavior of nfcManager by utilizing the nfcManager.start() function in react native testing library

In the process of developing my Android app, I encountered a need to read NFC tags. To accomplish this task, I decided to utilize the react-native-nfc-manager library. However, during my implementation, I faced two perplexing issues that have left me stump ...

Retrieve the chosen item to automatically fill in the input fields using Ionic 2 and Angular

My goal is to create a dropdown menu populated with a list of items, and when a product is selected, its price should automatically appear in the quantity field. <ion-item> <ion-label>Item</ion-label> <ion-select (ionChange)="getP ...

The error of 'illegal invocation' occurs when attempting to use input setCustomValidity with TypeScript

I am new to the Typescript world and currently converting one of my first React applications. I am facing an issue while trying to set custom messages on input validation using event.target.setCustomValidity. I keep getting an 'Illegal invocation&apo ...

What distinguishes Angular directives as classes rather than functions?

When using Ng directives within HTML tags (view), they appear to resemble functions that are called upon rather than instances of a class. It almost feels like they could be static methods that can be invoked without an instance of a class. Comin ...

The module './product' could not be located, resulting in error TS2307

app/product-detail.component.ts(2,22): error TS2307: Cannot find module './product'. I have tried several solutions but none of them seem to work for me. I am working on a demo app in Angular 2 and encountering this specific error. Any guidance ...

Vue.js with TypeScript: The property 'xxx' is not found on the type 'never'

I have a computed method that I am trying to execute: get dronesFiltered(){ const filtered = this.drones.filter((drone) => { return drone.id.toString().indexOf(this.filterId) > -1 && drone.name.toLowerCase().toString().in ...

Leveraging Typescript's robust type system to develop highly specific filter functions

I'm attempting to utilize the robust TypeScript type system in order to construct a highly typed 'filter' function that works on a collection (not just a simple array). Below is an illustration of what I am striving for: type ClassNames = &a ...

Creating a Union Type from a JavaScript Map in Typescript

I am struggling to create a union type based on the keys of a Map. Below is a simple example illustrating what I am attempting to achieve: const myMap = new Map ([ ['one', <IconOne/>], ['two', <IconTwo/>], ['three ...

Guide on adding up the value of a property within an array of objects using AngularJS

I am receiving an array of results from a Node.js API in my Angular app, and here is how it looks: <div *ngFor="let result of results"> <div *ngIf="result.harmattan.length > 0"> ... </div> <br> .. ...

Issue with Angular 5 Application - "Implementations cannot be declared in ambient contexts"

Recently in my Angular 5 project, I started encountering an issue with the observable object. Everything was working smoothly until about a week ago when I began receiving this error message: ERROR in node_modules/rxjs/Observable.d.ts(20,31): error TS1183 ...

What is the process for creating a unique Vee-Validate rule in TypeScript?

I am in the process of developing a unique VeeValidate rule for my VueJS component written in TypeScript. This rule is designed to validate two fields simultaneously, following the guidelines outlined in VeeValidate - Cross Field Validation. Here is a snip ...

Disallow the use of properties in a nested interface

Is there a way to define an interface or type that restricts a specific key in a child of the interface when used in union types? I am looking for the correct definition for Abc: type Abc = { someField: { prohibited?: never, }, }; type Use ...

While utilizing Ionic to upload images to a server, I encountered the FileTransferError error code 3

I have successfully uploaded an image from the gallery, but I am facing issues while uploading multiple images at once. Here is the code snippet I am using: pictureUpload(x){ // x represents the file path like - file:///storage/emulated/0/Download/palak ...

Encountering an issue with Next.js, Typescript, and mongoose when attempting to use `let cached = global.mongoose

I attempted to create a cached mongoose connection for my Next.js + Typescript application, but the code I used was: let cached = global.mongoose; if (!cached) { cached = global.mongoose = { conn: null, promise: null }; } The use of global.mongoose res ...