Home > Software design >  Make two parameters optional but both required if atleast one is specified in TypeScript
Make two parameters optional but both required if atleast one is specified in TypeScript

Time:12-08

I am trying to make a props type for a React component that can have certain fields but can also have additionally two more fields. But they have to be both specified, or not specifed at all.

I was trying to achieve this with the following example:

interface Base {
    f: string;
    d: string;
}

interface ExtendedBase extends Base {
    a: string;
    b: string;
}

const obj: Base | ExtendedBase = { // shouldn't this give an error that 'b' is missing?
    f: '',
    d: '',
    a: ''
}

I thought that with union it will require 2 Base fields or 4 ExtendedBase fields, but in the example above specifiying only 3 fields seems to satisfy the union, but why?

Any thoughts? Much appreciated.

CodePudding user response:

As an alternative to your type definitions, I would suggest making use of Discriminated unions. This way, you could refactor your code as:

interface Common {
    f: string;
    d: string;
}

interface Base extends Common {
    type: 'base'
}

interface ExtendedBase extends Common {
    type: 'extendedBase'
    a: string;
    b: string;
}

const obj: ExtendedBase | Base = { // gives an error that 'b' is missing
    type: 'extendedBase',
    f: '',
    d: '',
    a: '',
}

CodePudding user response:

I think, you can make use of Type Aliases
For Example,

type MyType = {
  a?: true,
  b?: string
} | {
  a?: false,
  b?: string
}

Here, if a is assigned/true then b will be required, and if you wish not to assign or keep a as false, then b won't be required.

Hope this helps somehow!
Good Luck :-D

CodePudding user response:

The obj variable as you defined it can contain a Base or an ExtendedBase but the constraint when assigning it is actually "each property that is in one type and the other must exist in the assigned value".

The actual keys that must be in a value being assigned to obj are only f and d, and you can assign { f: '', d: '', a: '' } because it has at least f and d.

If you want to keep this data structure, you could keep this solution and take advantage of the type narrowing which is based on the control-flow:

interface Base {
    f: string;
    d: string;
}

interface ExtendedBase extends Base {
    a: string;
    b: string;
}

const obj: Base | ExtendedBase = {
    f: '',
    d: '',
    a: '',
}

if(!('a' in obj)) {
    // obj is considered to be of type Base in this code block
    obj.a; // Property 'a' does not exist on type 'Base'.
    obj.b; // Property 'b' does not exist on type 'Base'.
    obj.d; // OK
    obj.f; // OK
} else {
    // obj is considered to be of type ExtendedBase in this code block
    obj.a; // OK
    obj.b; // OK
    obj.d; // OK
    obj.f; // OK
}
  • Related