I have a case where I'm frequently checking both the value of a boolean and another union type together. While they're logically connected and could hypothetically just check one, I need to check both so the TS compiler knows both types.
let multiple: boolean
let value: Array<string> | string
...
if (multiple && isArray(value)) {
// TS knows multiple === true and value is an Array
}
Is there a way to write a type checking function where the type predicate asserts multiple values?
Something like this:
// Not valid Typescript
function isMulti (
multi: boolean,
value: Array<string> | string
): (multi is true) && (value is Array<string>) {
return multi && isArray(value)
}
In most cases I can get by with just checking isArray, but there are cases where I want to reuse a check like the one above. Is this possible?
In response to @kaya3:
To give a more accurate use case, I have something similar to the following:
I could assign multiple and value to an object with a DiscriminatedUnion type, but that adds more complexity than I think this needs.
type ValueType<M extends boolean> = M extends true ? Array<string> : string;
interface PropTypes<M extends boolean> {
multiple: M,
// more properties
}
type DiscriminatedUnion = {
multiple: true,
value: Array<string>
} | {
multiple: false,
value: string
}
function myFunction<M extends boolean>({
multiple
}: PropTypes<M>) {
let value: ValueType<M>;
// ... later
if (multiple) {
// TS will not assert the type of `value` here
}
// Trying discriminated union
// TS Error: Type '{ multiple: boolean; value: string | string[]; }' is not assignable to type '{ multiple: false; value: string; }'
let obj: DiscriminatedUnion = {
multiple,
value
}
}
It isn't currently possible to solve your problem this way; @jcalz helpfully notes that there is an open feature request for it. That said, your problem is probably better solved in a different way.
For pretty much any question like "I have these two values, how can I tell Typescript their types are related?", the answer is to put them together as properties in an object. Then you can make the object's type a discriminated union, so that a test on one of the properties can narrow the type of the whole object.
type DiscriminatedUnion =
| {multiple: false, value: string}
| {multiple: true, value: string[]}
declare let obj: DiscriminatedUnion;
if(obj.multiple) {
// here, obj.value is narrowed to string[]
obj.value
}
Playground Link
With typescript you sometimes need to forego deconstructing objects until they can be properly discriminated. It often demands rethinking the flow. If you don't deconstruct the props right away, it can properly infer the type:
type Props = {
value: string[],
multiple: true
} | {
value: string,
multiple: false
}
function doSomethingAndDiscriminate(props:Props) {
if(props.multiple) {
const value = props.value[0]
return {somethingNew: value}
} else {
const value = props.value
return {somethingNew: value}
}
}
Playground link
In this example it properly infers the return type of both possibilities to be { somethingNew: string }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With