I need to make a component of tabs to able show tabs either in icon mode or plain simple text. Instead passing in variant, I thought maybe better let the Typescript to check whether one of the icons is having attribute iconName, and if so - enforce all the other items to include it as well (by throwing compiler error). If all objects has iconName, or all objects don't have it, it's valid.
Here an example and my attempt, to make it clearer:
type ObjectWithIcon<T> = T extends { iconName: string } ? T & { iconName: string } : T;
interface MyObject {
iconName?: string;
label: string;
}
// This will throw error on second object because of missing iconName
const arrayOfObjects: ObjectWithIcon<MyObject>[] = [
{ iconName: "box", label: "storage" },
{ label: "memory" }, // Error: Property 'iconName' is missing, currently NOT throwing an error
];
// This will not give any error because both has iconName
const arrayOfObjects2: ObjectWithIcon<MyObject>[] = [
{ iconName: "box", label: "storage" },
{ iconName: "circle", label: "memory" },
];
// This will not give any error because no object using iconName
const arrayOfObjects3: ObjectWithIcon<MyObject>[] = [
{ label: "storage" },
{ label: "memory" },
];
No matter how you define
ObjectWithIcon<T>, you don't want to use the typeObjectWithIcon<MyObject>[], because that's just a single plain array type. Such an array type can't possibly care whether all its elements do or do not have some particular property, the only thing it cares about is whether each element conforms toObjectWithIcon<MyObject>, independently of every other element in the array.Instead I think you want to use a union of array types, where you say "this is either an array of
MyObjects with aniconName, or an array ofMyObject's without aniconName.That is, you want a type equivalent to
TypeScript doesn't really directly encode "without a given property", but you can write "has an optional property of the impossible
nevertype", which is pretty close.Anyway, to that end, let's write some utility types to represent "with", "without" and the "either-or" array:
The
With<T, K>type says you've got something which is of typeT, and also (via intersection), something where theKproperty is required (via thePickutility type to grab the part with keyK, and via theRequiredutility type to say that property is required and not optional).The
Without<T, K>type says you've got something which is of typeT, and also something where theKproperty is optional of typenever(via thePartialutility type to say that the property is optional, and via theRecordutility type to say that we're talking about something with keyKand valuenever).And finally,ArrayAllWithOrAllWithout<T, K>says you've got something which is either an array ofWith<T, K>, or an array ofWithout<T, K>`.You can verify that
is equivalent to the manually written out
ObjectsAllWithOrAllWithoutIconfrom above. And that your examples therefore work as intended:Playground link to code