1

I have a function that checks to see if a generic type’s value exists in an array:

export function isValueInArray<T extends string>(
  values: T[],
  value: T | undefined
) {
  return value ? values.includes(value) : false;
}

It would then be used like this:

export type Status = 'OPEN' | 'CANCELED' | 'ACCEPTED' | 'INVALIDATED';

// typeof buyNowStatus === Status 
const isValid = isValueInArray(['OPEN'], buyNowStatus)

In this case it works in terms of auto-completing the first array argument. The issue is that we can pass in any string:

// no typescript error is thrown here 
const shouldBeInvalid = isValueInArray(['OPEN', 'ANY'], buyNowStatus)

One way to fix this would be to pass the Status in as a generic:

// ts errors out properly (but we must pass in a generic)
const isInvalid = isValueInArray<Status>(['OPEN', 'ANY'], buyNowStatus)

Is there a way to achieve this without needing to pass Status in as a generic? To somehow infer the Status type from buyNowStatus and for that to override the default T extends string behaviour in isValueInArray?

function checkStatus(status: Status) {
  // no typescript error thrown here either
  return isValueInArray(['ANY'], status);
}

1 Answer 1

1

You need to be generic over the whole array, and then value is typed as a derivation of that array.

You also need to make sure the array is the right type. A literal ['a', 'b'] will be inferred as string[]. BUt you want something like Status[].

For example:

export function isValueInArray<T extends string[]>(
  values: T,
  value: T[number] | undefined
) {
  return value ? values.includes(value) : false;
}

export type Status = 'OPEN' | 'CANCELED' | 'ACCEPTED' | 'INVALIDATED';
const statuses: Status[] = ['OPEN', 'ACCEPTED']

isValueInArray(statuses, 'OPEN') // fine
isValueInArray(statuses, 'foo') // error

See playground


Now I assumed that you wanted to ensure value is a valid member type of values. But it sounds like you want to ensure that the array values only has strings that match the type of value.

You can do that, too:

export function isValueInArray<
  Value extends string,
  Arr extends Value[]
>(
  values: Arr,
  value: Value | undefined
) {
  return value ? values.includes(value) : false;
}

export type Status = 'OPEN' | 'CANCELED' | 'ACCEPTED' | 'INVALIDATED';

declare const status: Status | undefined

isValueInArray(['CANCELED'], status) // fine
isValueInArray(['ANY'], status) // error

Now there are two generics. Value is the type of the second argument. And then Arr is the first argument that must be an array of the Value type.

See playground

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

4 Comments

Thanks! That works when called directly, however when called in the context of a function doesn’t seem to throw any errors (I’ve added in a function example called checkStatus at the end of my question above to illustrate it).
I guess it depends on which parameter you want to use as the proper type. See my update.
One slight thing that’s missing is the handing of undefined being passed in for the value arg
Just add | undefined then tsplay.dev/wXQM8N

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.