I have a situation where I would like to handle two situations with the same component based on a boolean value.
I'm using React, Typescript and Formik.
- A simple selectbox where the value saved onto the Formik context is a single object.
- A multiSelectbox where the value saved onto the Formik context is an array of objects.
Example of the general structure:
export interface SelectboxProps<ObjectT> {
multiSelection?: boolean;
}
export function Selectbox<ValuesT, ObjectT>({
multiSelection = false,
...props
}: SelecboxProps<ObjectT>) {
const [field, meta] = useField<ObjectT[] | ObjectT | null)>({ ...props, name });
// Some handleChange functions and so forth.
}
Conceptually, I would like it to work like this:
const [field, meta] = useField< multiSelection ? (ObjectT[]) : (ObjectT | null)>({ ...props, name });
However, as I understand it this is not possible. My second plan of attack was to overload the component function, but this has not proven to be successful.
I've read through the following without getting any wiser: https://charlypoly.com/publications/typescript-generics-and-overloads https://www.fullstacklabs.co/blog/overload-typescript-react-component-interfaces-prop-values Defining a generic based on react prop Typescript React component with function overload and generic params
CodePudding user response:
To handle the type for Props, you can do:
type Props = ({ multiple: true; value: Item[] } | { multiple: false; value: Item }) & { label: string, ... }
Then if you check props.multiple, it will typeguard value to be an array of Items. However, things like state will still likely end up typed as Item | Item[]
as typescript cannot track that far effectively (and even its possible your component changes from multiple=true -> false, and the state is still an array!). For this purpose, it's likely simplest to handle both cases within the component generally, and assert types if needed.
Another common option I've seen is:
type Props<IsMultiple extends boolean> = {
isMultiple: IsMultiple;
value: IsMultiple extends true ? Item[] : Item;
}
The issue with this approach is that while Props<true>
and Props<false>
both type correctly (and thus Typescript generally infers that {isMultiple: false, value: []} isn't allowed as it assumes Props<false>
), Props<boolean>
technically allows isMultiple and value to not match. Because of this, checking if props.isMultiple will not typeguard value either.