I'm trying to set up a type for a custom input component that will inherit properties based on the type of element to be displayed. Essentially the logic is what the title states
type A AND EITHER B or C or D or E
meaning A is always applied, then either B or C or D or E follow. Here's what I have so far.
interface GenericInputElementProps {
id: string;
hidden: boolean;
label: string;
onInput: (
id: string,
value: string | number,
isValid: boolean
) => {
type: string;
value: string | number;
inputId: string;
isValid: boolean;
};
validators?: { type: string; configVal?: number }[];
initialValue?: {
initialValue: string;
initialValid: boolean;
};
}
type InputElementProps =
| (
| GenericInputElementProps
| {
element: 'input';
type: string;
placeholder: string;
errorText: string;
}
)
| {
element: 'textarea';
rows: number;
placeholder: string;
errorText: string;
}
| { element: 'number'; type: 'number' }
| { element: 'checkbox'; type: 'checkbox' }
| { element: 'select'; sizes: ISizes[] };
but this doesn't work because typescript thinks I'm using GenericInputElementProps
and element='input'
but doesn't consider the other options. I'm sure there is a way around this but I cannot find out what pattern I can use.
If you have types A
, B
, C
, D
, and E
, and you want to represent "A
and something which is either B
or C
or D
or E
", then you can do so by turning "and" into an intersection and "or" into a union, like:
type Okay = A & (B | C | D | E);
// type Okay = A & (B | C | D | E)
Those parentheses are important, because intersections bind more tightly than unions in TypeScript syntax:
type Oops = A & B | C | D | E;
// type Oops = (A & B) | C | D | E
That implies you should do something like:
type InputElementProps = GenericInputElementProps & (
{
element: 'input';
type: string;
placeholder: string;
errorText: string;
} | {
element: 'textarea';
rows: number;
placeholder: string;
errorText: string;
} | {
element: 'number'; type: 'number'
} | {
element: 'checkbox'; type: 'checkbox'
} | {
element: 'select'; sizes: ISizes[]
}
);
If you want to break those apart into their own interfaces it's up to you:
interface TextElementProps {
element: 'input';
type: string;
placeholder: string;
errorText: string;
};
interface TextAreaElementProps {
element: 'textarea';
rows: number;
placeholder: string;
errorText: string;
};
interface NumberElementProps {
element: 'number';
type: 'number';
};
interface CheckboxElementProps {
element: 'checkbox';
type: 'checkbox';
};
interface SelectElementProps {
element: 'select';
sizes: ISizes[];
};
type InputElementProps = GenericInputElementProps & (
TextElementProps |
TextAreaElementProps |
NumberElementProps |
CheckboxElementProps |
SelectElementProps
);