Home > Mobile >  TypeScript - how to use an index on a variable type
TypeScript - how to use an index on a variable type

Time:12-07

I'm trying to pass a string index to use on one of two state types. It throws the following error:

    type Index = 'name' | 'length' | 'width' | 'depth' | 'height'
    
    interface StateOne {
        name: string
        length: number
    }
    
    interface StateTwo {
        width: number
        depth: number
        height: number
    }
    
    interface SwitchProps {
        state: StateOne | StateTwo
        index: Index
    }
    
    function testFunction({state, index}: SwitchProps) {
        const test = state[index]
    }
    
//TS7053: Element implicitly has an 'any' type because expression of type 'Index' can't be used to index type 'StateOne | StateTwo'. 
//Property 'name' does not exist on type 'StateOne | StateTwo'.

How would you write this to get around the problem?

This works, but it is a bad workaround:

const test = state[index as unknown as keyof typeof state]

CodePudding user response:

You can use generics and restrict index to be a valid key of state:

function testFunction<K extends keyof T, T>({ state, index }: { state: T, index: K }) {
  return state[index]
}

declare const state1: StateOne;
declare const state2: StateTwo;

testFunction({ state: state1, index: 'name' }) // string
testFunction({ state: state2, index: 'width' }) // number

// Error: Type '"width"' is not assignable to type 'keyof StateOne'
testFunction({ state: state1, index: 'width' })

Playground

CodePudding user response:

You can use a type predicate to discriminate between your types:

TS Playground link

interface StateOne {
  name: string;
  length: number;
}

interface StateTwo extends Record<'depth' | 'height' | 'width', number> {}

type PropVariant<T> = {
  state: T;
  index: keyof T;
};

type SwitchProps = PropVariant<StateOne> | PropVariant<StateTwo>;

function isStateOne (props: SwitchProps): props is PropVariant<StateOne> {
  /** `"name"` only exists in SwitchOne, so if this returns `true`, we know it's that type */
  return 'name' in props.state;
}

function testFunction (props: SwitchProps): void {
  if (isStateOne(props)) {
    const value = props.state[props.index]; // string | number
    console.log(value);
    const {length} = props.state; // string
  }
  else {
    const value = props.state[props.index]; // number
    console.log(value);
    const {depth} = props.state; // number
  }
}
  • Related