Require a parameter in the return function when the generic is not null in the caller within Typescript

In TypeScript version 5.0.2

I am working on a function that returns an array of 3 functions. I want the purchase function to be typed in such a way that it only requires a requirement parameter if the specified product has one (as indicated in the products type object). Currently, both the purchaseSoda() and purchaseBeer() functions require a requirements parameter. However, there are no requirements for soda, so I expect only the purchaseBeer() function to throw an error since no requirements object is passed to it.

I do not want to have to call a method that does not require a parameter like this: purchaseSoda(undefined);

type InventoryItem<Product, Restrictions = undefined> = {
  product: Product,
  restrictions: Restrictions
}

type Beverage = { sizeInOunces: number, price: number };
type Book = { title: string, price: number }
type AgeRequirement = { age: number }

type products = {
  "soda": InventoryItem<Beverage>,
  "beer": InventoryItem<Beverage, AgeRequirement>,
  "comic": InventoryItem<Book>,
  "Adult Magazine": InventoryItem<Book, AgeRequirement>
};

const getItem = <
  productName extends keyof products,
  item extends products[productName],
  product extends item["product"],
  restrictions extends item["restrictions"]
>
  (name: productName): [
    (item: product) => void,
    (restrictions: restrictions) => product,
    () => void
  ] => {

  const addItemToInventory = (item: product) => { throw new Error("omitted for stackoverflow"); }
  const cancelPurchase = () => { throw new Error("omitted for stackoverflow"); }

  const purchase = (restrictions: restrictions): product => { throw new Error("omitted for stackoverflow"); }

  return [addItemToInventory, purchase, cancelPurchase];
}

const [addSoda, purchaseSoda, cancelPurchaseSoda] = getItem("soda");
const [addBeer, purchaseBeer, cancelPurchaseBeer] = getItem("beer");

purchaseSoda();  // Shouldn't Require a Param
purchaseBeer();  // Should Error here, because the beer product has an age requirement.
purchaseBeer({ age: 21 }); // Should require a param

I have attempted using never and null as default assignments for the InventoryItem.

I have converted the getItem and purchase methods into functions with overrides.

I have modified the return signature of getItem:

  (name: productName): [
    (item: product) => void,
    (() => product) |
    ((restrictions: restrictions) => product),
    () => void
  ] => {

I have changed the signatures of the purchase method to make the parameter optional (using '?'):

  (name: productName): [
    (item: product) => void,
    (restrictions?: restrictions) => product,
    () => void
  ] => {

  const addItemToInventory = (item: product) => { throw new Error("omitted for stackoverflow"); }
  const cancelPurchase = () => { throw new Error("omitted for stackoverflow"); }

  const purchase = (restrictions?: restrictions): product => { throw new Error("omitted for stackoverflow"); }

Although this allows me to call purchaseSoda() without a parameter, it also allows me to call purchaseBeer() without one. However, purchaseBeer() has an age requirement that needs to be included. Simply adding '?' did not solve my problem.

I have gone through several docs in attempts to solve this issue.

After spending a few hours experimenting with this, I feel stuck and would greatly appreciate any help or insights. Thank you for your time.

Answer №1

TypeScript doesn't automatically assume that a function parameter which accepts undefined can be considered as an optional parameter. Therefore, if you desire such functionality, you must perform type manipulations to express it.

The function type is essentially something like

(restrictions: R) => P

This implies that an argument for restrictions is always required, even if undefined can be assigned to R.

If you prefer a function type where undefined falls within the domain of R, then it should be defined as (restrictions?: R) => P (note the ?), otherwise it will be (restrictions: R) => P. There are multiple ways to achieve this, but here's one method using a conditional type for the entire function type:

type UndefIsOptional<R, P> =
    undefined extends R ?
    (restrictions?: R) => P :
    (restrictions: R) => P;

Now, use this function type instead:

const getItem = <
    K extends keyof Products,
    I extends Products[K],
    P extends I["product"],
    R extends I["restrictions"]
>
    (name: K): [
        (item: P) => void,
        UndefIsOptional<R, P>,
        () => void
    ] => {

    const addItemToInventory = (item: P) => { throw new Error("omitted"); }
    const cancelPurchase = () => { throw new Error("omitted"); }

    const purchase: UndefIsOptional<R, P> = (r?: R): P => 
      { throw new Error("omitted"); }

    return [addItemToInventory, purchase, cancelPurchase];
}

Test it out with the following code:

const [addSoda, purchaseSoda, cancelPurchaseSoda] = getItem("soda");
purchaseSoda();  // okay
purchaseSoda({ age: 21 });  // error

const [addBeer, purchaseBeer, cancelPurchaseBeer] = getItem("beer");
purchaseBeer();  // error
purchaseBeer({ age: 21 }); // okay

It seems to work well. Also, consider another scenario where the requirement itself is optional:

⋯
"optionalId": InventoryItem<Book, AgeRequirement | undefined>;
⋯

Both ways of calling it are valid:

const [, purchaseOpt,] = getItem("optionalId");
purchaseOpt(); // okay
purchaseOpt({ age: 21 }); // okay

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

Svelte language switcher experiencing technical difficulties

Currently delving into Svelte 3, I embarked on a project intended to be shared on GitHub in English. However, I realized that some of my friends do not speak English. To accommodate different language preferences, I decided to create a language switcher. H ...

Error: To execute NPX command locally from Google Maps API documentation, make sure to call npm.load(callback) as required

Attempting to execute the Google Maps API example locally using this command: npx @googlemaps/js-samples init directions-waypoints googlemapssample However, every time I try to run the npx command locally, it fails after a short period and I encounter the ...

How can I resolve the issue of TypeScript AngularJS component modal where the function this.$modalInstance.dismiss is not working?

After converting one of my user data entry forms into a uib-modal, I encountered an issue when trying to close the modal using the "cancel" button. The error message "this.$modalInstance.dismiss is not a function" keeps popping up. The same problem occurs ...

Tips for maintaining license comments in TypeScript 2.5

When creating JavaScript libraries using TypeScript v2.5 and tsc, it is important to include license comments in the built files. However, the removeComments configuration in the tsconfig.json file can remove certain comments, including license comments. ...

Trouble with Mui theme not being applied when inside a wrapper component

In my project using React with typescript and MUI version 5.4.2, I have been attempting to manage all styles in a single file by enclosing everything inside my App.tsx component. Problem: The custom MUI theme is not being applied throughout my application ...

How can the Calendar Ant Design showcase events that have fluctuating dates?

Seeking a solution to display events on an Ant Design Calendar using dateCellRender with dates stored in a variable object. Here's an example of the object: [ { "id": 1, "content": "Example", & ...

Angular Bootstrap causes misalignment of table column headings based on different properties in object

I have an object with two properties: person and vehicle. Both properties consist of arrays of data. I have separate column headings for the person and vehicle properties and display the data in tabular form. However, the column headings for both propertie ...

Issues may arise in Typescript when trying to return an array of data from a redux createAsyncThunk function

Below is the code I am using to retrieve a list of users: export const fetchUserById = createAsyncThunk( "users/fetchById", async (_, { rejectWithValue, fulfillWithValue }) => { try { const response = await fetch(`https://reqres. ...

Utilizing Node.js Functions for Sharing Database Queries

When it comes to sharing DB query code among multiple Node.js Express controller methods, finding the best practice can be challenging. Many samples available online don't delve deeply into this specific aspect. For instance, let's consider a ge ...

When I try to integrate Three.js into my React application, it mysteriously causes my root HTML element

While attempting to utilize Three.js in Typescript react, I successfully rendered a Dodecahedron figure along with random stars. However, my intention was to incorporate some markup into my Three.js scene using React. Unfortunately, when I render the Three ...

Retrieving a data type from the key values of deeply nested objects

I'm currently working with JSON data that contains nested objects, and my goal is to extract the unique set of second-level keys as a type. For instance: const json = { 'alice': { 'dogs': 1, 'birds': 4 ...

Access to the server has been restricted due to CORS policy blocking: No 'Access-Control-Allow-Origin'

I’m currently encountering an issue with displaying API content in Angular and I’m at a loss on how to troubleshoot it and move forward. At this moment, my main objective is to simply view the URL data on my interface. Does anyone have any insights or ...

Retrieve an array of information and convert it into an object using Angular

My previous data is displaying correctly in the chart, as shown below: @Component({ selector: 'app-inpout-bar-chart', templateUrl: './inpout-bar-chart.component.html', styleUrls: ['./inpout-bar-chart.component.scss'] }) exp ...

Guide to customizing Material UI theme using Typescript in a separate file

Trying to customize Material UI theme overrides can be a bit tricky, as seen in the example below: // theme.ts const theme: Theme = createMuiTheme({ overrides: { MuiButton: { root: { display: 'inline-block', fontWeigh ...

The error message "Property is not found on type 'Object'" suggests that the property being accessed does not

I wrote a function called getAll getAll<T>() { return this.http.get(`${environment.apiUrl}/products`); } Here is how I am invoking it: this.productService.getAll() .pipe(first()) .subscribe(products => { debugger let s ...

Angular Dynamic CSS Library by BPNM

I'm currently working with the Bpmn library to create a diagram. However, I've encountered an issue where I need to hide the palette in the diagram view dynamically. I attempted to achieve this by using @ViewChild in my TypeScript file to target ...

Leverage the Nuxeo client SDK with Angular 6 for seamless integration with RESTClient in

Looking to integrate the Nuxeo ClientSdk with my Angular 6 client to consume its REST API, but facing issues due to the lack of typescript definitions for this JavaScript package. Tried importing the library into my project using the following code snippe ...

Exploring the distinction between "() => void" and "() => {}" in programming

Exploring TS types, I defined the following: type type1 = () => {} type type2 = () => void Then, I created variables using these types: const customType1: type1 = () => { } const customType2: type2 = () => { } The issue surfaced as "Type ...

The call stack size has been exceeded in Next.js, resulting in a RangeError

Currently attempting to deploy my project on vercel.com but encountering an error specifically with 3 pages that have no internal errors. An error occurred while prerendering the page "/applications". For more information, visit: https://nextjs.org/docs/me ...

Exploring the process of selecting checkboxes in Angular 6

I'm currently learning Angular 6 and I have a requirement to mark checkboxes based on specific IDs from two arrays: this.skillArray = [ {ID: 1, name: "Diving"}, {ID: 2, name: "Firefighting"}, {ID: 3, name: "Treatment"}, ...