0

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]
2
  • 1
    state[index].name looks wrong. 'name' would be passed through index, no? Commented Dec 6, 2021 at 12:14
  • Yes, missed that. I've updated it. 'name' is the first property in the index where it encounters a problem. Commented Dec 6, 2021 at 12:21

2 Answers 2

1

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

Sign up to request clarification or add additional context in comments.

2 Comments

You can change the function's generic to <K extends keyof T, T extends StateOne | StateTwo> to strictly type the state parameter
Yeah, but no reason to do so
0

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
  }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.