If you have two or more types without any common properties (ie., they're mutually exclusive), you can't create a discriminated union with them:
type Props = { a: boolean } | { b: string }
const obj1: Props = { a: true } // ✅ valid
const obj2: Props = { b: "hello" } // ✅ valid
const obj3: Props = { a: true, b: "hello" } // ✅ valid, but we'd like this to be an error, ie., you can either include a or b, but not both
Solution
If you are dealing with two types, then you can use a custom XOR type (source):
type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };
type XOR<T, U> = (T | U) extends object ? (Without<T, U> & U) | (Without<U, T> & T) : T | U;
// USAGE
type Props = XOR<{ a: boolean }, { b: string }>
const obj1: Props = { a: true } // ✅ valid
const obj2: Props = { b: "hello" } // ✅ valid
const obj3: Props = { a: true, b: "hello" } // ❌ error
You can alternatively use the ts-xor package to do exactly that.
If you have more than two types, you can nest XOR operators, like this:
type Props = XOR<{ c: number }, XOR<{ a: boolean }, { b: string }>>
The drawback with this XOR type is that the errors shown are cryptic, so perhaps avoid this if you're creating types for a library.
Top comments (0)