1

I have been trying to get the specific type based on an function argument but I can't find any way in typescript.

This "step" argument is actually a key in the slice

type Slice1 = {
    info: string
}

type Slice2 = {
    info: string
}

type MyAppState = {
    step1: Slice1;
    step2: Slice2;
}

function useStep<T>(step: keyof T): T[typeof step] {
  const [state] = useContext(ExternalContext);
  return state[step];
}

const step1 = useStep<MyAppState>('step1'); <-- **should return Slice1 type**

I want to get the correct type depending on the key.

3
  • 2
    would it make sense to pass anything other than MyAppState to the useStep function? The solution is probably to use <T extends keyof MyAppState>(step: T): MyAppState[T] but I'm not sure what your goal is. Commented Sep 29, 2020 at 22:00
  • The important thing is that the key itself needs to be generic, rather than just keyof T, so that your return type can be based on this specific key. In the unlikely case that ExternalContext can have multiple types of state, you can make both the state and the key generic like so function useStep<T, K extends keyof T>(step: K): T[K], but you would no longer be able to specify T without also specifying K, so you couldn't type useStep<MyAppState>('step1'). If the only possible type of state is MyAppState then only the key needs to be generic and it will be what the others have said. Commented Sep 29, 2020 at 23:02
  • My goal is to keep MyAppState outside useStep hook definition because I want this state to be specified by the consumer. The ideal scenario might be: const mySlice = useStep<AnyState>('keyInAnyState') --> only the slice type Commented Sep 30, 2020 at 2:40

1 Answer 1

1

Is the fact that we hardcode MyAppState okay with you?:

function useStep<TKey extends keyof MyAppState>(step: TKey): MyAppState[TKey] {
  const [state] = useContext(ExternalContext);
  return state[step];
}

const step1 = useStep('step1'); // <-- returns Slice1 type

EDIT: Since you don't want to hardcode MyAppState in your type, you can use a curried function:

function useStep<T>() {
  return <TKey extends keyof T>(step: TKey): T[TKey] => {
    const [state] = useContext(ExternalContext);
    return state[step];
  }
}

const step1 = useStep<MyAppState>()('step1'); // <-- returns Slice1 type
Sign up to request clarification or add additional context in comments.

3 Comments

My goal is to keep MyAppState outside useStep hook definition because I want this to be specified by the consumer. The ideal scenario might be: const mySlice = useStep<AnyState>('keyInAnyState') --> only the slice type
Wow nice approach, thank you. I came out with this solution inspired by redux: selectors ` function useStep<TState = {}, TSelected = unknown>( selector: (state: TState) => TSelected):TSelected { const [state] = useContext(ExternalContext); return state[step]; } ` ` const step1 = useStep((state: MyAppState) => state.step1); `
@AlexisPatiño The issue with your solution is that it doesn't check whether the key of the interface is used: useStep((state: MyAppState) => 12); is valid and it returns number.

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.