How can we define the types of two arguments in a function using Typescript?

Is there a way to achieve this functionality?

type SomeType = {
  name: string;
  quantity: number;
};
const someFunc = (
  keyName: keyof SomeType /* what should be here? */,
  keyValue: SomeType[keyof SomeType] /* what should be here? */
) => {
  // ...
};
someFunc("name", "John") // OK
someFunc("name", 10) // must be error
someFunc("quantity", "John") // must be error
someFunc("quantity", 10) // OK

I attempted to implement it as follows:

...
const someFunc = (
  key: keyof SomeType,
  value: SomeType[keyof SomeType]
)
...

However, my implementation is not working, and I now understand why. I am unsure on how to proceed with implementing this.

Answer №1

Creating a foolproof solution for this problem is quite challenging. One approach is to use a generic type parameter like so:

type CustomType = {
    label: string;
    value: number;
};
const customFunc = <T extends keyof CustomType>(
    property: T,
    data: CustomType[T],
) => {
    // ...
};

By utilizing this method, you can ensure that certain examples will work correctly:

customFunc("label", "Sample") // Valid
customFunc("label", 123) // Expected error
customFunc("value", "Data") // Expected error
customFunc("value", 456) // Correct

However, due to the nature of unions, this approach is not completely foolproof. By specifying an explicit type argument, it's possible to circumvent the intended restrictions:

customFunc<"label" | "value">("label", 123) // Incorrect but no error raised

This has implications on how the function behaves internally, as narrowing down the type of property does not necessarily narrow down the type of data. For further insights, refer to this related discussion.

Answer №2

A slightly imperfect but still effective improvement on the solution by @T.J Crowder

This approach restricts the use of unions but doesn't validate the correctness of value types

type UnionToIntersection<U> =
    (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never

type IsUnion<T> = [T] extends [UnionToIntersection<T>] ? false : true

type SomeType = {
    name: string;
    quantity: number;
    age: number
};
const someFunc = <Key extends keyof SomeType>(
    key: IsUnion<Key> extends true ? "No Union!" : Key,
    value: SomeType[Key],
) => {
    // ...
};

someFunc<"name" | "quantity">("name", 10) // Error at argument 1 instead of argument 2, still a valid error scenario
someFunc<"quantity" | "age">("quantity", 10) // Should not show an error since both quantity and age are numbers

// Functionality remains intact
someFunc("name", "John") // OK
someFunc("name", 10) // Desired error generated
someFunc("quantity", "John") // Desired error shown
someFunc("quantity", 10) // No issues

https://i.sstatic.net/GlJCj.png IsUnion

The code may flag errors in unexpected places and overlook certain cases, but ultimately it guides you towards the correct type

While not perfect, it functions effectively, demonstrating the importance of prioritizing accuracy over assumption

However, this method may be challenging for newcomers, leading to frustration with troubleshooting that could potentially shake their confidence

Explaining this concept is complex and perhaps TypeScript should bear responsibility for such intricacies

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

Issue with TranslateModule Configuration malfunctioning

I have put together a file specifically for managing ngx-translate configuration: import { Http } from '@angular/http'; import { TranslateHttpLoader } from '@ngx-translate/http-loader'; import { TranslateLoader, TranslateModu ...

Retrieve the value of a local variable in the ngOnInit function from a different function

Recently, I've started working with Angular and TypeScript. I am facing an issue where I need to access a local variable that is declared in the ngOnInit function from outside it, but I'm not quite sure how to achieve this correctly. This variabl ...

npm encountered an issue when attempting to install a package from a local directory: EISDIR: illegal operation on a directory, read

While attempting to add my compiled TypeScript output as a local package using npm, this error appears: $ npm install --save ../app/out npm ERR! eisdir EISDIR: illegal operation on a directory, read npm ERR! eisdir This is most likely not a problem wit ...

Exploring Angular 9: Experimenting with iterating over a collection of objects and performing validations

Forgive me if this is a basic question, but I am new to Angular 9 and json. I am attempting to iterate through a list and only create a table row if the correct ID matches the ID passed from the previous page link. I am struggling to make the if statement ...

Template literal types in TypeScript and Visual Studio Code: An unbeatable duo

I'm encountering an issue while attempting to utilize literal types in Visual Studio Code. When following the example from the documentation, https://i.stack.imgur.com/V6njl.png eslint is flagging an error in the code (Parsing error: Type expected.e ...

Guide to accessing component methods within slots using the Vue 3 Composition API

I have child components within a slot in a parent component and I am trying to call methods on them. Here are the steps I followed: Use useSlots to retrieve the child components as objects Expose the method in the child component using defineExpose Call t ...

I'm having trouble configuring the header in my Node/Express route

Using Node and the Express framework for my backend, along with React for my frontend, all coded in Typescript. The elastic search client is responsible for fetching data on the backend, but I don't believe that's where the issue lies. I'm ...

How come the hasOwnProperty function does not remove objects of type {}?

I am working with a complex type called ReactNode from the version @types/react 16.9.17 and TypeScript v3.7.4. import React, { ReactNode } from 'react'; My goal is to reduce this type to a new type that includes the property children. To achie ...

Submitting information to a server

I have a simple Angular 6 app with follow and unfollow buttons. When you click follow, the number increases. I want to save these follower numbers to a JSON server. Here is the link to the JSON server documentation: JSON Server Documentation To see a dem ...

What is the proper type for an object and an array of strings?

We have an array example below. const sampleArray = [ {names: ['example1', 'example2']}, 'another', 'final' ]; Additionally, here is a type error example. The error message reads as follows: "Type 'string ...

Subtracting Arrays Containing Duplicates

Imagine having two arrays defined like this: const A = ['Mo', 'Tu', 'We', 'Thu', 'Fr'] const B = ['Mo', 'Mo', 'Mo', 'Tu', 'Thu', 'Fr', 'Sa&ap ...

Exploring Node Stream.Writable Extension in Typescript 4.8

I'm attempting to craft a basic class that implements Node stream.Writable, but it seems like I can't quite grasp the correct syntax - the compiler keeps throwing errors: https://i.stack.imgur.com/UT5Mt.png https://i.stack.imgur.com/Z81eX.png ...

Translate from one category to a different one

I often encounter a common issue - how can I efficiently convert one type to another? For instance, extending an object received from the server with UI-specific properties. interface RawData { id: number someOtherData: string } interface ViewData ex ...

The value specified as type '{ value: BigNumber; }' cannot be assigned to the parameter type 'Overrides & { from?: string | Promise<string> | undefined; }'

I am currently working on a smart contract using Solidity (version 0.8.0) at my buildspace project. Below is a snippet of my code written in TypeScript (4.5.x)/JavaScript and Node.js 16.13.x: ... const waveContractFactory = await hre.ethers.getContractFact ...

Transforming query language from jQuery to Pure JavaScript

I have the following jQuery code that I am attempting to remove and convert into standard JavaScript: $('.switch').click(()=>{ $([".light [class*='-light']", ".dark [class*='-dark']"]).each((i,ele)=& ...

Error in Angular-CLI: The return type of a public method from an exported class is referencing the name 'ErrorObservable' from an external module, but it cannot be named as such

Upon completing the development of an app that mirrors an existing Angular 2 (non-cli) application, I am encountering errors in several of my files now that the project has been transitioned to Angular-CLI. I am puzzled as to why these errors are arising i ...

Using TypeScript to Declare Third Party Modules in Quasar

I'm currently trying to integrate Dropzone-vue into my Quasar project. However, I've encountered an issue as I can't directly install and declare it in a main.js file due to the lack of one in Quasar's structure. Additionally, an error ...

Next.js 14 useEffect firing twice upon page load

Having an issue with a client component in next js that is calling an API twice at page load using useEffect. Here's the code for the client component: 'use client'; import { useState, useEffect } from 'react'; import { useInView ...

What is the process for performing type checking on a block of TypeScript code stored in memory?

I am currently integrating TypeScript support into my project called Data-Forge Notebook. My goal is to compile, perform type checking, and evaluate snippets of TypeScript code within the application. Compiling the code seems straightforward using the tr ...

Here is a method to transform the JSON object into a string as demonstrated below:

Presented below is a JSON object: { "category": "music", "location": { "city": "Braga" }, "date": { "start": { "$gte": "2017-05-01T18:30:00.000Z" }, "end": { "$lt": "2017-05-12T18:30:00.000Z" } } } I am looking t ...