Optional parameter on interface based on type argument in typescript

1.4k Views Asked by At

I would like to create a type or interface in typescript, that takes an optional generic parameter. To make it optional, I use the default generic parameter syntax:

interface IdData {
  id: string;
}

interface WizardContext<T = undefined> {
  step: number;
  items: T extends IdData ? T[] : undefined
}

The concept here is that if a more specific type is built from WizardContext, and the type argument T satisfies the condition T extends IdData, then that specific type should have a property items, which is T[]. Some examples of what I'm trying to achieve:

// ------- Defining some extended types -------------:

interface User extends IdData {
  name: string;
}

type UserWizardContext = WizardContext<User>;

// ------- Trying to satisfy those types: ------------

// Works
const userWizardProps: UserWizardContext = {
  step: 2,
  items: [{ name: 'name1', id: 'id1' }]
}
// Errors as expected, as `items` is missing
const userWizardProps2: UserWizardContext = {
  step: 2,
}

// Errors, even though items should be allowed to be undefined
const simpleWizardProps: WizardContext = {
    step: 2
}

// I have to declare items as undefined, when it should be able to just be omitted
const simpleWizardProps2: WizardContext = {
    step: 2,
    items: undefined
}

In the last case, even though simpleWizardProps2 uses WizardContext with no type argument definition, it still errors if I don't manually include items: undefined. This is essentially the difference between an optional vs undefined parameter. I do not want items to be optional, because then in various other places where it is meant to be defined, I get Object is possibly 'undefined' issues.

I have tried doing this with types instead of interfaces as well:

type WizardContextType<T = undefined> = {
  step: number;
} & (T extends IdData ? { items: T[]} : {})

But this ultimately makes no difference. If the type argument passed to WizardContext or WizardContextType does not extend IdData, items is still expected to be declared as undefined, when the goal is to actually omit it.

TS playground demonstrating the issue

How can I create this optional property based on whether or not the type argument satisfies the condition?

1

There are 1 best solutions below

2
Andr3 On

in this case if T does not extends IdData the type of itens property is undefined but still required. to create a optional property you should to use

interface WizardContext<T = undefined> {
  step: number;
  items?: T extends IdData ? T[] : undefined
}