Typescript conditionally mandatory arguments

In search of a way to create a function with optional parameters in TypeScript? Look no further! Imagine needing certain parameters to be optional, but if they are provided, then others become required.

Take this whimsical example:

type PersonInfo = {
  name: string; 
  age: number; 
  pets?: boolean; 
  petNames?: string[]; 
}
const recordPersonInfo = (options: PersonInfo) => {
  database.save(options); 
}

Here's the catch - while the pets property can be left out, if it is specified as true, then petNames becomes mandatory. Question is, how do we convey this logic to Typescript?

Answer №1

If you want to handle different types of options using a discriminant, consider using a union type. In this case, the property pets acts as the discriminant. When you check if(options.pets), within that if block, options will be effectively narrowed down to only include the petNames property.

type PersonInfo = {
  name: string; 
  age: number;
} & ({
  pets?: false;
} | {
  pets: true; 
  petNames: string[]; 
})

const recordPersonInfo = (options: PersonInfo) => {
  database.save(options); 
}

Answer №2

By utilizing function overloading, you have the ability to achieve something along these lines:

type PersonInfoWithPets = PersonInfo & { pets: true; petNames: string[] };
type PersonInfoWithoutPets = PersonInfo & { pets?: false; petNames?: undefined };


function recordPersonInfo(options: PersonInfoWithPets): void;
function recordPersonInfo(options: PersonInfoWithoutPets): void;
function recordPersonInfo(options: PersonInfo): void {

}

recordPersonInfo({
    name: 'name',
    age: 0,
});
recordPersonInfo({
    name: 'name',
    age: 0,
    pets: false,
});
recordPersonInfo({
    name: 'name',
    age: 0,
    pets: true,
    petNames: [],
});
// This errors because `pets` but no `petNames`
recordPersonInfo({
    name: 'name',
    age: 0,
    pets: true,
});
// This errors because `petNames` but no `pets`
recordPersonInfo({
    name: 'name',
    age: 0,
    petNames: ['hi'],
});
// NOTE: This also errors because `pets` is a boolean here, not strictly true/false
recordPersonInfo(null! as PersonInfo);

In all honesty, TypeScript may not offer a flawless solution for this issue. In this particular scenario, one could argue that "the presence (and emptiness) of petNames should replace the need for the pets field." Nevertheless, we managed to work around it with two overloads, though more complex situations might necessitate additional overloads.

Naturally, you could opt to thoroughly document the function and throw an error if incorrect input is still provided.

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 utilization of a collective interface/data type within an Angular application`

I created a HeaderComponent that requires an object with the structure of {title: string, short_desc: string} as its input property. @Component({ selector: 'header', templateUrl: './header.component.html', styleUrls: ['./hea ...

How can you specify the active route in Angular?

I am curious about whether it is possible to set the active route from a script instead of just from the HTML template. Let me provide an example: @Component({ template: `<input type="button" (click)="back()" value="back" ...

Link the ngModel input to an object within an ngFor iteration

Looking to create a dynamic form using an array that includes FieldLabel and DataModel references. I want to use the DataModel as an object reference, so when the user updates an input field, the referenced model is updated. I have searched extensively bu ...

Sharing references in React Native using TypeScript: The (property) React.MutableRefObject<null>.current is potentially null

I'm working on a React Native form with multiple fields, and I want the focus to move from one field to the next when the user validates the input using the virtual keyboard. This is what I have so far: <NamedTextInput name={&apo ...

Uniting 2 streams to create a single observable

I am in the process of merging 2 different Observables. The first Observable contains a ShoppingCart class, while the second one holds a list of ShoppingItems. My goal is to map the Observable with shopping cart items (Observable<ShoppingItems) to the i ...

Can a function interface be implemented in TypeScript successfully?

I am interested in implementing this functionality: class MyFunc extends ((s: string) => boolean) { ... } This would allow an instance of MyFunc to act as a function that takes a string input and returns a boolean value, like so: const f = new MyFunc ...

What is the correct approach to managing Sequelize validation errors effectively?

I am working on a basic REST API using Typescript, Koa, and Sequelize. If the client sends an invalid PUT request with empty fields for "title" or "author", it currently returns a 500 error. I would prefer to respond with a '400 Bad Request' ins ...

What is the best way to synchronize API definitions between the server and client using TypeScript?

My setup involves a server (TypeScript, NestJS) and a client (TypeScript, Angular) that communicate with each other. Right now, I have the API response DTO classes defined in both the server to output data and in the client to decode the responses into a ...

Introduce a specialized hierarchical data structure known as a nested Record type, which progressively ref

In my system, the permissions are defined as an array of strings: const stringVals = [ 'create:user', 'update:user', 'delete:user', 'create:document', 'update:document', 'delete:document&ap ...

Can an interface be designed to have the option of containing either one property or another?

I am in need of an interface that resembles the following structure: interface EitherOr { first: string; second: number; } However, I want to make sure that this interface can only have either the property first or second. Do you think achieving this ...

Is there a way to retrieve the number of swipe up interactions from Instagram story insights using the graph API

Is there a way to retrieve the swipe up count displayed in Instagram insights? Since Facebook does not provide this data through their Graph API, how can I access it? I have already tried scraping without success and I am looking for a solution using eith ...

Expand the data retrieved from the database in node.js to include additional fields, not just the id

When creating a login using the code provided, only the user's ID is returned. The challenge now is how to retrieve another field from the database. I specifically require the "header" field from the database. Within the onSubmit function of the for ...

Finding the final day of a specific year using the moment library

When it comes to determining the last day of a year, hard-coding the date as December 31st seems like a simple solution. While there are various methods using date, js, and jquery, I am tasked with working on an Angular project which requires me to use mom ...

Dealing with server-side errors while utilizing react-query and formik

This login page utilizes formik and I am encountering some issues: const handleLogin = () => { const login = useLoginMutation(); return ( <div> <Formik initialValues={{ email: "", password: "" }} ...

Encountering an issue while attempting to input a URL into the Iframe Src in Angular 2

When I click to dynamically add a URL into an iframe src, I encounter the following error message: Error: Uncaught (in promise): Error: Cannot match any routes. URL Segment: 'SafeValue%20must%20use%20%5Bproperty%5D' To ensure the safety of the ...

Typescript (ionic) loading animation that keeps users engaged while waiting for the data to be loaded

I'm looking to include an animation on the screen while waiting for the projects to load. constructor( public platform: Platform, private network: NetworkService, public navContrl: NavController, public modalCtrl: Moda ...

`Filter an array retrieved from the backend in a TypeScript environment`

I have asked other questions in the past, but I received unhelpful answers. I am still looking for proper solutions. Currently, I am retrieving an array from the backend using Redux. const { movies, message } = useAppSelector(state => state.movies); ...

Directly mapping packages to Typescript source code in the package.json files of a monorepo

How can I properly configure the package.json file in an npm monorepo to ensure that locally referenced packages resolve directly to their .ts files for IDE and build tooling compatibility (such as vscode, tsx, ts-node, vite, jest, tsc, etc.)? I want to a ...

What are the steps to restrict a user from accessing a specific website?

In my Vue.js project, I've implemented a function that hides a specific menu item for users with insufficient permissions: <a :href="href" @click="navigate" v-if="hideMenuItem()"> // some code </a> hideMe ...

In my current project, I am working with Knockout and TypeScript but I am encountering difficulties in firing the window-resize event

Instead of using jquery, I prefer working with a custom handler for the $(window).resize(function () { ... event. If there is a way to achieve this without relying on jquery, please feel free to share it in the comments below. The code snippet below show ...