Creating a TypeScript generic parameter that is optional if no value is provided

When working with an interface, I have the following:

interface HandlerEvent<T = void> {
  data: T   // This parameter should always be required
}

I want to make data a required field if a value is passed when calling the function, otherwise it should be optional.

const actionHandler = ({}: HandlerEvent):void => { }

actionHandler({})  // Fails because data is required. How can I eliminate this error without specifying data?
actionHandler({data: undefined })  // Seems unnecessary in my opinion

So, I aim to be able to call the function without providing parameters for the generic, without being prompted for data. However, if I do provide a value for T, then data should be mandatory. While I could use data?: T, I am looking for an alternative to manually checking the presence of data in the code. I initially thought that setting the default parameter (void in this case) might resolve the issue, but it still requires me to pass data.

Currently, I receive errors even when trying to call actionHandler() without needing data. Is there something I'm missing about passing arguments to the generic?

I've come across several discussions on this topic, but couldn't find a definitive solution - they seem to have been closed without a clear answer, unless I missed it buried in the comments.

Referencing How to pass optional parameters while omitting some other optional parameters?

I did discover a related article that involves wrapping the generic - but is there a simpler approach that I may have overlooked?

Play around with TypeScript in this TS Playground link

Answer №1

To make the data optional only when undefined can be assigned to T, you can utilize a conditional type:

type HandlerEvent<T = void> = undefined extends T ? { data?: T } : { data: T }

This will produce the desired behavior:

type H = HandlerEvent
// type H = { data?: void | undefined; }

const actionHandler = (x: HandlerEvent): void => { }
actionHandler({}); // acceptable
actionHandler({ data: undefined }) // acceptable

If you need to apply this logic to multiple properties, you can create an UndefinedToOptional<T> type function that handles object types by making any property optional if undefined is assignable to it.

The implementation of the type function may seem complex but it achieves the desired outcome:

type UndefinedToOptional<T> = { [K in keyof T]-?:
    (x: undefined extends T[K] ? { [P in K]?: T[K] } : { [P in K]: T[K] }) => void
}[keyof T] extends (x: infer I) => void ?
    I extends infer U ? { [K in keyof U]: U[K] } : never : never

Although not aesthetically pleasing, it effectively processes object types without index or call signatures:

type Test = UndefinedToOptional<
    { a: string, b: number | undefined, c: unknown, d: boolean, e: any }
>;
// type Test = { 
//   a: string; b?: number | undefined; c?: unknown; d: boolean; e?: any; 
// } 

You can then define HandlerEvent using this type function like so:

type HandlerEvent<T = void> = UndefinedToOptional<{ data: T }>

This leads to the same result as before.

Explore code on TypeScript Playground

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

Encountering an issue when trying to access a class's member variable within the "then" function of

Trying to develop a simple contact list app using Angular 4 and facing an issue with the delete functionality. When attempting to delete a contact, the contacts variable inside the 'then' function is returning undefined. The main problem lies w ...

experiencing an excessive amount of rerenders when trying to utilize the

When I call the contacts function from the main return, everything seems fine. However, I encounter an error at this point: const showContacts = React.useCallback( (data: UsersQueryHookResult) => { if (data) { return ( < ...

Array behavior subjects are used to store multiple values and emit new

When running the code snippet provided, the result displayed is [object object] instead of the expected [{a,b},{b,c}]. // Service bData : BehaviorSubject<any[]> = new BehaviorSubject<any[]>([]); bDataSubject = this.bData.asObservable(); / ...

Validating patterns in Angular without using a form

Seeking guidance on validating user input in Angular6 PrimeNG pInputText for a specific URL pattern, such as , possibly triggered on blur event. This particular field used to be part of a form but has since been relocated to a more complex 6-part form int ...

When using Angular with mdbootstrap, the mdb-tabs directive will move to the end if the ngIf condition is true

Currently facing a challenge with a significant amount of code here. It is referenced as follows: "ng-uikit-pro-standard": "file:ng-uikit-pro-standard-8.3.0.tgz", I am attempting to display a tab between 1 and 3 if a certain condition ...

Setting the root directory and output directory can be a bit tricky when dealing with source code scattered across multiple folders. Here's

Utilizing TypeScript in my Node.js project, I previously had a directory structure that looked like this: node_modules src -Models -Routes -Middlewares -Controllers -index.ts package.json tsconfig.json In ...

issue with mongoose virtual populate (unable to retrieve populated field)

During my project using mongoose with typescript, I encountered an issue with adding a virtual called subdomains to populate data from another collection. Although it worked without any errors, I found that I couldn't directly print the populated data ...

Custom options titled MUI Palette - The property 'primary' is not found in the 'TypeBackground' type

I am currently working on expanding the MUI palette to include my own custom properties. Here is the code I have been using: declare module '@mui/material/styles' { interface Palette { border: Palette['primary'] background: Pa ...

Exploring the possibilities of retrieving Cognitive data from Lambda events using Node.js and Typescript

import { APIGatewayEventDefaultAuthorizerContext, APIGatewayProxyEvent, any } from 'aws-lambda'; export async function myHandler(event: APIGatewayProxyEvent, context: APIGatewayEventDefaultAuthorizerContext,): Promise<any> { console.log( ...

TypeOrm throws an error: ColumnTypeUndefinedError - the column type for #name is unspecified and cannot be inferred

An error occurred while trying to decorate the target with decorator: ^ ColumnTypeUndefinedError: The column type for Author#name is not defined and cannot be guessed. To fix this issue, make sure to enable "emitDecoratorMetadata" option in tsconfig.j ...

Delegating in Typescript/Angular2 without using an arrow function

Why is the result of TypeScript delegates not equal? someProperty: any; someActionsWithItems(item: any) { this.someProperty = "test"; } // When using 'this', it works fine: this.array.forEach(item => this.someActionsWithItems(item)); / ...

A Unique Identifier in Kotlin

In my typescript class, I have a member that accepts any as the name: interface ControlTagType { type?: String | null; [name: string]: any } class ControlTag { tagSource: String | null = null; tag: ControlTagType | null = null; } expor ...

Creating a Responsive Canvas for PDF.JS Viewer

I am facing an issue with displaying a PDF using the PDFJS library on my page. The fixed scale that I have set is causing the canvas, where the PDF is rendered, to not be responsive and fit within the bootstrap grid column width. Below is the HTML code sni ...

Using TypeScript to specify a limited set of required fields

Can a custom type constraint be created to ensure that a type includes certain non-optional keys from an object, but not all keys? For instance: class Bar { key1: number key2: Object key3: <another type> } const Y = { key1: 'foo' ...

What is the significance of utilizing an empty value `[]` for a typed array interface instead of using an empty `{}` for a typed object interface?

Why can I initialize friends below as an empty array [], but not do the same for session with an empty object {}? Is there a way to use the empty object without needing to make all keys optional in the interface? const initialState: { friends: Array< ...

Angular: sending the user input to the component

I'm trying to link the value of an HTML input field to a variable 'numToAdd' in my component, and then increment the 'numToAdd' variable by 1. However, I'm facing difficulties passing the input value to the component variable. ...

Creating a duplicate form when a user clicks using JavaScript

https://i.sstatic.net/xhwy7.png createNewForm() { const newDiv = document.createElement('div'); newDiv.appendChild(document.createTextNode('Some different text')); newDiv.setAttribute('class', 'bg-second ...

Is it necessary for NestJS exception filters to handle event emitter errors?

While conducting some manual tests on a revamped NestJS application, I stumbled upon an unusual behavior exhibited by Nest's global exception filter. The developer of this project has implemented the following as an exception filter: import { Excep ...

Deciphering the significance of [index: string] in TypeScript

Just starting out with Typescript here. Recently came across this code snippet in a project on GitHub - can someone explain its significance? interface info { [index: string]: any; } ...

Using Typescript, develop a function within an entity to verify the value of a property

In my Angular 7 app, I have an entity defined in my typescript file as follows: export class FeedbackType { id: number; name: String; } I am looking to create a function within this entity that checks the value of a property. For example: feedba ...