Specify the callback parameter for the function based on the provided string literal argument

To simplify the typing process and ensure correct types are obtained when the function is used, I am working on creating functions with minimal explicit typing at the point of use. The function structure is shown below, where the type of the arg in the callback function should depend on the string passed as the fntype argument.

fn(fntype: string, callback: (arg: any) => void): void;

For instance,

fn('foo', (foo) => {
    foo.somethingInTheFooInterface;
}

fn('bar', (bar) => {
    bar.somethingInTheBarInterface;
}

Here are the types that have been established:

type FooType = "FooType";
const FooType: FooType = 'FooType';

type BarType = 'BarType';
const BarType: BarType = 'BarType';

type ActionTypes = FooType | BarType;

interface Action<T> {
    type: T;
}

interface FooInterface extends Action<FooType> {
    somethingOnTheFooInterface: string;
}

interface BarInterface extends Action<BarType> {
    somethingOnTheBarInterface: string;
}

type CallbackTypes = FooInterface | BarInterface;

type Callback<T extends CallbackTypes> = (action: T) => void;

function fn<T extends CallbackTypes, U extends ActionTypes>(actionType: U, cb: Callback<T>): void;

function fn (actionType, cb) {
    cb();
}

While these types work effectively when explicitly utilized:

// Works fine if we explicitly type the arg
fn(FooType, (arg: FooInterface) => {
    arg.somethingOnTheFooInterface
});

// Works fine if we define the generics when calling 
fn<FooInterface, FooType>(FooType, arg => {
    arg.somethingOnTheFooInterface;
});

Unfortunately, the callback is not typed based on the first argument:

// TypeError as arg is typed as the union type CallbackTypes
fn(FooType, arg => {
    arg.somethingOnTheFooInterface
})

If you have any suggestions or guidance on how to achieve this desired typing behavior, your input would be greatly appreciated.

Answer №1

It appears to me that what you're suggesting may be a bit excessive.
Your objective could likely be achieved through the use of signature overloading:

interface FooInterface {
    propertyOnTheFooInterface: string;
}

interface BarInterface {
    propertyOnTheBarInterface: string;
}

fn(fntype: "FooType", action: (arg: FooInterface) => void): void;
fn(fntype: "BarType", action: (arg: BarInterface) => void): void;
fn(type: string, action: (arg: any) => void) { ... }

Answer №2

With the introduction of Typescript 2.9 and conditional types, achieving this without function overloading is now possible:

type FooType = "FooType";
const FooType: FooType = "FooType";

type BarType = "BarType";
const BarType: BarType = "BarType";

type ActionTypes = FooType | BarType;

interface Action<T> {
    type: T;
}

interface FooInterface extends Action<FooType> {
    somethingOnTheFooInterface: string;
}

interface BarInterface extends Action<BarType> {
    somethingOnTheBarInterface: string;
}

type CallbackTypes<T> =
    T extends FooType ? FooInterface :
    T extends BarType ? BarInterface :
    never;

type Callback<T> = (action: T) => void;

function fn<T extends ActionTypes, U extends CallbackTypes<T>>(actionType: T, cb: Callback<U>) {
    cb({} as any);
};

fn(FooType, arg => {
    arg.somethingOnTheFooInterface
})

fn(BarType, arg => {
    arg.somethingOnTheBarInterface
})

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

The time that was constructed is not a valid function

I am currently working on a puppeteer script that interacts with my browser extensions and navigates to a specific page. It clicks on a particular extension button and fills in an input. Unfortunately, I am encountering an issue with the following error me ...

Issue encountered while conducting tests with Jest and React Testing Library on a React component containing an SVG: the type is not recognized in React.jsx

In my Next.js 12.1.4 project, I am using Typescript, React Testing Library, and SVGR for importing icons like this: import ChevronLeftIcon from './chevron-left.svg' The issue arises when running a test on a component that includes an SVG import, ...

Obtaining the sub-domain on a Next.js page

Looking at my pages/index.tsx file in Next.js, the code structure is as follows: import { ApolloProvider } from "@apollo/react-hooks" import Client from "../db" import Header from "../components/Header" export default function Index() { return <A ...

Initiate the printing process by executing the window.print() function. As the document is being

Here's the code snippet : <body> <div class="headerCont noprint"> <div class="headerHold"> <div class="logoCont"><img src="images/logo.jpg" width="104" height="74" alt="Logo"> ...

Transfer the selected date value from one datePicker (start date) to another datePicker (end date) using React JS

Need assistance with setting up two datePickers (StartDate & EndDate). The goal is to pass the selected StartDate value to the EndDate so that users can only choose a date after the StartDate. For instance, if StartDate is set to December 10, the EndD ...

Where the package.json file resides

Is there a designated location for the package.json file in a project, like within the project directory? Where should the package.json file be located in a multi-component project? What is the significance of having version 0.0.0 in th ...

VueJs seems to be having trouble with vertical chat scrolling functionality

I need assistance with implementing chat scrolling in my VUE JS application. I would like the messages to automatically scroll to the bottom of the page each time a new message is received or when the page loads. Currently, I have a function that contains ...

How to seamlessly incorporate Polymer Web Components into a Typescript-based React application?

Struggling to implement a Polymer Web Components tooltip feature into a React App coded in TypeScript. Encountering an error during compilation: Error: Property 'paper-tooltip' does not exist on type 'JSX.IntrinsicElements' To resolve ...

Using CKEditor5 to Capture and Edit Key Presses

I'm currently working on capturing input from a CKEditor5 within an Angular application using TypeScript. While I am able to successfully display the CKEditor and confirm its presence through logging, I am facing difficulties in capturing the actual i ...

Create a Referral Program page within a swapping interface similar to the functionalities found in platforms like PancakeSwap, PantherSwap, and

Currently, my goal is to create a referral program page similar to the one found at . I have explored the source code on GitHub for the PantherSwap interface, but unfortunately, I did not come across any references to that specific section. Would you be ...

Issue with AngularJS for loop incorrectly incrementing when there is a single item in the array

Within my Angular application, there is a snippet of code that looks like this: for (var i = 0; i < $scope.itemList.length; i++) { if ($scope.itemList[i].serialNumber == quickCode) { console.log(i) returnsService.getNoReceiptErrorMe ...

Is it possible to retrieve duplicate objects within the same array?

My array contains multiple objects. arr = [ {name: 'xyz', age: 13, }, {name: 'abc', age: 15, }, {name: 'abc', age: 15, }] I am seeking a solution to identify and remove duplicates in this array. Is it possible to achieve th ...

What steps should be taken to transform this Jquery Ajax code into Pure Javascript Fetch?

I am looking to convert this Jquery Ajax snippet to Fetch using Pure Javascript. Can you provide assistance? I attempted this previously, but my code did not function properly. I even posted a question about it here. That is why I am hoping for your help ...

Is it more suitable for the response logic to be implemented within the saga or the reducer?

Consider this scenario: export function* incrementAsync(action) { try { const res = yield call(Api.signin.create, action.payload); yield put({ type: USER_SIGN_IN_FETCH_SUCCESS, payload: res.data.auth }); } catch (e) { yie ...

Transforming Danish currency into numerical digits effortlessly

My current currency is 3.477,60 kr and I need to incorporate it into my JavaScript code for additional price calculation logic. I have tried using the following code to format it, but unfortunately, it returns NaN in the alert message. var currency = "3. ...

Is it possible to only make the text in a Bootstrap button act like a link, instead of the whole button itself?

As I start learning HTML and CSS with the help of Bootstrap, I'm encountering an issue with button functionality. The text within the button is acting like a link, but the entire button should be clickable. I'm not sure if I need more than just t ...

Is there a way for me to store the current router in a state for later use

I am currently working on implementing conditional styling with 2 different headers. My goal is to save the current router page into a state. Here's my code snippet: const [page, setPage] = useState("black"); const data = { page, setPage, ...

Displaying AJAX data in Django using an HTML table

My Django template currently has the following AJAX script running: function create_table() { $.ajax({ method: "GET", url: "/api/data/", success: function(data){ console.log('button clicked') ...

What are the typical methods for implementing middleware in Node.js using the Express and Connect frameworks?

Exploring the proper way to utilize middlewares in a growing Node.js web project using Express and Connect. While there are middlewares that handle requests globally, there are often specific tasks such as processing incoming data that are only relevant t ...

Retrieve the callback function assigned to an eventEmitter in Angular 4

Is there a way to retrieve the function passed to the Event Emitter? Within my component, I am passing a function to an event emitter as shown below: <component (onClick)='function(parameter)'></component> I have set up an @Output ...