Implement a function parameter as required or optional depending on another parameter in Typescript

Just diving into the world of TypeScript, I am attempting to implement type-checking for a function that has an optional third argument. Depending on another parameter, this third argument may or may not be utilized.

I have designated it as optional but encountered some issues:

  1. An error is triggered.
  2. Even if the error doesn't occur, I am curious if there is a way to dynamically switch the required/optional status of the parameter trailer based on another parameter (vehicleType).

Here's a sample code snippet I put together to demonstrate my dilemma:

enum VehicleType {
  Car,
  Pickup
}

type Vehicle = {
  weight: number;
  length: number;
};

type Trailer = {
  weight: number;
  length: number;
};

function vehicle(
  vehicleType: VehicleType,
  vehicle: Vehicle,
  trailer?: Trailer
) {
  switch (vehicleType) {
    case VehicleType.Car:
      return `${vehicle.length} ${vehicle.weight}`;
    case VehicleType.Pickup:
      return `${vehicle.length + trailer.length} ${
        vehicle.weight + trailer?.weight
      }`;
  }
}

Running this code results in the same error cropping up twice:

Object is possibly 'undefined'. For trailer. object

Is there a method to instruct the compiler to mandate the presence of trailer if the type is Pickup, and disregard it when the type is Car?

Answer №1

Based on the question, it seems that when vehicleType is Pickup, the trailer argument is mandatory.

The problem arises because TypeScript is unaware of this requirement; to TypeScript, calling

vehicle(VehicleType.Pickup, someVehicle)
without a trailer is acceptable.

To address this issue, you can specify valid parameter combinations using function overloads; while not a complete solution, it helps in defining expectations:

function vehicle(vehicleType: VehicleType.Car, vehicle: Vehicle): string;
function vehicle(vehicleType: VehicleType.Pickup, vehicle: Vehicle, trailer: Trailer): string;
function vehicle(vehicleType: VehicleType, vehicle: Vehicle, trailer?: Trailer): string {
    switch (vehicleType) {
        case VehicleType.Car:
            return `${vehicle.length} ${vehicle.weight}`;
        case VehicleType.Pickup:
            assertNotNullish(trailer);
            return `${vehicle.length + trailer.length} ${vehicle.weight + trailer.weight}`;
    }
}
...

Answer №2

A better approach would be to handle the issue of making the third argument conditionally nullable internally, rather than trying to manage it directly. This can be done by either throwing an exception or using the Nullish Coalescing operator ??

function vehicle(
  vehicleType: VehicleType,
  vehicle: Vehicle,
  trailer?: Trailer
) {
  switch (vehicleType) {
    case VehicleType.Car:
      return `${vehicle.length} ${vehicle.weight}`;
    case VehicleType.Pickup:
      return `${vehicle.length + (trailer?.length ?? 0)} ${
        vehicle.weight + (trailer?.weight ?? 0)
      }`;
  }
}

This solution works effectively in all cases

console.log(vehicle(VehicleType.Car, {weight:100, length:20}))
console.log(vehicle(VehicleType.Pickup, {weight:100, length:20}, {weight:10, length:5}))
console.log(vehicle(VehicleType.Pickup, {weight:100, length:20}))

Playground link

If the exception route is preferred, checking for null means avoiding potential null warnings from TypeScript

function vehicle(
  vehicleType: VehicleType,
  vehicle: Vehicle,
  trailer?: Trailer
) {
  switch (vehicleType) {
    case VehicleType.Car:
      return `${vehicle.length} ${vehicle.weight}`;
    case VehicleType.Pickup:
      if(!trailer){
        throw "Trailer must be specified when VehicleType is Pickup"
      }
      return `${vehicle.length + trailer.length} ${
        vehicle.weight + trailer.weight
      }`;
  }
}

Playground link

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

Steps for assigning the TAB key functionality within a text area

Is there a way to capture the TAB key press within a text area, allowing for indentation of text when the user uses it? ...

Using Intersection Observer in Typescript causes a bug when utilized with useRef

Currently, I have a text animation that is functioning flawlessly. However, my goal now is to implement an Intersection Observer so that the animation only triggers when I scroll down to the designated box. To achieve this, I utilized the useRef React hoo ...

What causes two identical generic types to produce varying results?

Can you help me understand the logic behind this TS compiler situation? I'm having trouble distinguishing between LookUp and LookUpWrong, and I can't comprehend why LookUpWrong always results in never. type LookUp<U, T extends string> = U e ...

Launching a web application on Vercel, the back-end has yet to be initialized

I am currently working on a Next.js/TypeScript web application project hosted on Vercel's free tier. To run my project, I use npm run dev in the app folder to start the front end and then navigate to the back-end folder in another terminal and run npm ...

zod - Mastering the Art of Dive Picking

Working with zod and fastify, my UserModel includes the username and device properties. The username is a string, while the device consists of "name", "id", and "verified" fields in an object (DeviceModel). For the sign-up process, I need to return the co ...

Using TypeScript in Angular to make a function call can result in an endless loop being created

In my current use case, I am aiming to update the array in both the if and else scenarios. The primary array, referred to as cis, appears as follows: https://i.sstatic.net/esbb8.png .html <sample-container [cis]="filterCi(currentBaseline.cis, c ...

The Chrome developer console is alerting that the Typescript file needs to be updated and is currently

Just made an update to my typescript file in my app and ran it. Source maps are enabled. When I check in Chrome by pressing F12 and browsing to the script, I see the .ts file without the recent function I added, but the .js file does have it. I tried fo ...

TypeORM is unable to locate the default connection within a class

I have encountered an issue while trying to incorporate TypeORM within a class. It seems to be unable to locate the default connection despite awaiting the connection. I have double-checked the configuration and tested it with .then(), which did work succe ...

What is the best way to interweave my objects within this tree using recursion?

I am working on creating a new function called customAdd() that will build a nested tree structure like the one shown below: let obj = [] let obj1 = { key: "detail1Tests", id: "94d3d1a2c3d8c4e1d77011a7162a23576e7d8a30d6beeabfadcee5df0876bb0e" } ...

Information is not recognized as a valid attribute

Despite following other answers, I am still encountering an error. Can you help me figure out what's wrong? To provide some context, here is the code for my component: import { Component, OnInit, Input } from '@angular/core'; @Component({ ...

Guide on troubleshooting Node TypeScript in Visual Studio Code when the JavaScript source is stored in a separate directory

When using Visual Studio Code, I am able to debug and step through the TypeScript source code of Main.ts. This is possible as long as the JavaScript and map files are located in the same folder as the TypeScript source. This setup works well in this struc ...

Unusual elective attributes found in TypeScript classes

Out of the blue, I started getting a ts2322 error with the code below. Everything was working fine in the Typescript playground. I have reviewed it multiple times but can't seem to find any issues. What could be causing the problem? The software ver ...

Combining platform-express and platform-fastify for optimal performance

I am currently working on a NestJS application and my goal is to upload files using the package @types/multer. However, I encountered an issue while following the guidelines from the official documentation: https://i.sstatic.net/JCX1B.png Upon starting ...

Are union types strictly enforced?

Is it expected for this to not work as intended? class Animal { } class Person { } type MyUnion = Number | Person; var list: Array<MyUnion> = [ "aaa", 2, new Animal() ]; // Is this supposed to fail? var x: MyUnion = "jjj"; // Should this actually ...

Angular's observables were unable to be subscribed to in either the constructor or ngOnInit() functions

Currently, I am incorporating an observable concept into my application. In this setup, a service is called by component1 to emit an event that is then subscribed to by component 2. Below is the code snippet for reference: Service Code export class Mes ...

Is there a way to modify the button exclusively within the div where it was pressed?

I plan to incorporate three buttons in my project (Download, Edit, and Upload). Upon clicking the Download button, a function is triggered, changing the button to Edit. Clicking the Edit button will then change it to Upload, and finally, clicking the Uplo ...

Procedure for distributing proportions

Imagine having an object like this: const data = { bills: 10, rent: 40, food: 50, } The total is 100 (or 100%). If we update the bills to a value of 20, the other properties should adjust accordingly, for example: { bills: 20, re ...

Reading a single line of text synchronously in Node JS

I have a scenario where I need to process a file named data.txt that contains thousands of lines. My objective is to read each line, extract specific data, and store it in an array. To achieve this, I am using the readline and fs.createReadStream functions ...

Mismatched data types for function arguments

const x: Example = { toY: (y: Maple) => { return y.p; } }; interface Example { toY: (y: Pine) => void; } interface Pine { c: string; } interface Maple extends Pine { p: boolean; } Despite the warning for interface names ...

Is it possible to conditionally trigger useLazyQuery in RTK Query?

Is it possible to obtain trigger from useLazyQuery conditionally? const [trigger] = props.useLazySearchQuery(); My objective is to retrieve trigger only when useLazySearchQuery is provided in the props. One way I can achieve this is by using const [ ...