Dynamically create type based on nested interface property

Given the following setup:

enum FormGroups {
  customer = 'customer',
  address = 'address',
}

interface Customer {
  'firstName': string;
}

interface Address {
  'street': string;
}

interface SomeFormEvent {
  valid: boolean;
  forms: {
    [FormGroups.customer]: {
      values: Customer,
    },
    [FormGroups.address]: {
      values: Address,
    }
  }
}

Now the following is what I would like to use:

type Forms = Customer | Address

However, this is static. If in the future a form is added in the SomeFormEvent like:

interface SomeFormEvent {
  valid: boolean;
  forms: {
    [FormGroups.customer]: {
      values: Customer,
    },
    [FormGroups.address]: {
      values: Address,
    },
    // a form is added here:
    [FormGroups.preferences]: {
      values: Preferences,
    },
  }
}

I’d have to manually update the ‘Forms’ type:

// manually have to add 'Preferences'
type Forms = Customer | Address | Preferences

Is there a more dynamic way to create the Forms type?

I’ve tried the following:

enum FormGroups {
  customer = 'customer',
  address = 'address',
}

interface Customer {
  'firstName': string;
}

interface Address {
  'street': string;
}


interface SomeFormEvent {
  valid: boolean;
  forms: {
    [FormGroups.customer]: {
      values: Customer,
    },
    [FormGroups.address]: {
      values: Address,
    },
  }
}

type Forms = { [K in keyof SomeFormEvent['forms']]: SomeFormEvent['forms'][K]['values'] }

function foo(): Forms {
    return {
        // error: Type '{ firstName: string; street: string; }' is 
        //   not assignable to type 'Forms'. Object literal may only 
        //   specify known properties, and 'firstName' does not exist 
        //   in type 'Forms'.

        firstName: 'sdfdsf',
        street: 'sdffds'
    }
}

But that does not work.

>Solution :

Yes, we can create this union using indexed access types.

type Forms = SomeFormEvent["forms"][keyof SomeFormEvent["forms"]]["values"]
//   ^? Customer | Address

Playground

Leave a Reply