LazioTibijczyk
LazioTibijczyk

Reputation: 1937

New interface based on an interface's keys

I am trying to create yet another interface from the ImportAccountsState but this time omitting the StateVariable interface. I want to do it the TypeScript way without defining it manually again.

interface StateVariable<T> {
  value: T;
  setValue: (value: T) => void;
}

interface ImportAccountsState {
  isDialogOpen: StateVariable<boolean>;
  organization: StateVariable<string>;
  isOrganizationValid: StateVariable<boolean>;
}

I want my end result to be like so

interface ImportAccountsState {
  isDialogOpen: boolean;
  organization: string;
  isOrganizationValid: boolean;
}

I kind of made a start with keyof ImportAccountsState but couldn't figure out how to map it to whatever type is inside the angle brackets <>.

Upvotes: 0

Views: 50

Answers (2)

You can use mapped types for this purpose

interface StateVariable<T> {
    value: T;
    setValue: (value: T) => void;
}

interface ImportAccountsState {
    isDialogOpen: StateVariable<boolean>;
    organization: StateVariable<string>;
    isOrganizationValid: StateVariable<boolean>;
}


type Result = {
    [Prop in keyof ImportAccountsState]: ImportAccountsState[Prop]['value']
}

Docs

Would it be far more complex if ImportAccountsState was generic?

interface StateVariable<T> {
    value: T;
    setValue: (value: T) => void;
}
// not sure where you want to use T generic
interface ImportAccountsState {
    isDialogOpen: StateVariable<boolean>;
    organization: StateVariable<string>;
    isOrganizationValid: StateVariable<boolean>;
}


type Result<T extends Record<PropertyKey, { value: unknown }>> = {
    [Prop in keyof T]: T[Prop]['value']
}

I am getting an error when using Result Just use type instead of interface:

// not sure where you want to use T generic
type ImportAccountsState = {
    isDialogOpen: StateVariable<boolean>;
    organization: StateVariable<string>;
    isOrganizationValid: StateVariable<boolean>;
}

types have index signature if you want to constraint it

OR move constraint inside the iteration:

interface StateVariable<T> {
    value: T;
    setValue: (value: T) => void;
}
// not sure where you want to use T generic
interface ImportAccountsState {
    isDialogOpen: StateVariable<boolean>;
    organization: StateVariable<string>;
    isOrganizationValid: StateVariable<boolean>;
}


type Util<T> = {
    [Prop in keyof T]: T[Prop] extends { value: unknown } ? T[Prop]['value'] : never
}

type Result = Util<ImportAccountsState> // ok

Upvotes: 1

spender
spender

Reputation: 120380

You can create a type to extract the generic type from StateVariable<T>:

type ExtractGeneric<T extends StateVariable<any>> = 
    T extends StateVariable<infer TT> ? TT : never;

Here, we can be sure that because T is constrained to be of type StateVariable<any> that the never branch above will never happen because the type-checker will spew.

Now we can create a mapped type that makes use of the ExtractGeneric type:

type MappedStateVariableRecord<T extends Record<keyof T, StateVariable<any>>> = {
  [P in keyof T]: ExtractGeneric<T[P]>
}

then use this type on your ImportAccountsState:

type MappedImportAccountsState = MappedStateVariableRecord<ImportAccountsState>

See Playground Link

Upvotes: 1

Related Questions