Reputation: 1356
How can I make TypeScript know which of the prop types the component is receiving?
E.g if multiple
is true, I want TypeScript to expect that selectValue
is an array of string or if its not present then TypeScript should expect selectValue
to be a string.
I really dislike having to cast my props to the appropriate type for TypeScript to be happy.
Here is the current code for my custom <Select>
component:
import React, { ReactNode } from 'react';
import MultipleSelect from './MultipleSelect';
import SingleSelect from './SingleSelect';
interface MultipleSelectProps {
selectValue: string[];
multiple: true;
onSelectChange: (values: string[]) => void;
renderValue?: (value: string[]) => React.ReactNode;
}
interface SingleSelectProps {
selectValue: string;
onSelectChange: (values: string) => void;
multiple?: never;
renderValue?: (value: string) => React.ReactNode;
}
type SelectProps = SingleSelectProps | MultipleSelectProps;
const Select: React.FC<SelectProps> = ({
selectValue,
multiple,
onSelectChange,
renderValue,
children,
}) => {
if (multiple) {
const multipleSelectValue = selectValue as string[];
const multipleSelectChange = onSelectChange as (value: string[]) => void;
const multipleRenderValue = renderValue as (value: string[]) => ReactNode;
return (
<MultipleSelect
selectValue={multipleSelectValue}
onSelectChange={multipleSelectChange}
renderValue={multipleRenderValue}
>
{children}
</MultipleSelect>
);
}
const singleSelectValue = selectValue as string;
const singleSelectChange = onSelectChange as (value: string) => void;
const singleRenderValue = renderValue as (value: string) => ReactNode;
return (
<SingleSelect
selectValue={singleSelectValue}
onSelectChange={singleSelectChange}
renderValue={singleRenderValue}
>
{children}
</SingleSelect>
);
};
export default Select;
I want to write it something like this instead:
import React, { ReactNode } from 'react';
import MultipleSelect from './MultipleSelect';
import SingleSelect from './SingleSelect';
interface MultipleSelectProps {
selectValue: string[];
multiple: true;
onSelectChange: (values: string[]) => void;
renderValue?: (value: string[]) => React.ReactNode;
}
interface SingleSelectProps {
selectValue: string;
onSelectChange: (values: string) => void;
multiple?: never;
renderValue?: (value: string) => React.ReactNode;
}
type SelectProps = SingleSelectProps | MultipleSelectProps;
const Select: React.FC<SelectProps> = ({
selectValue,
multiple,
onSelectChange,
renderValue,
children,
}) => {
return (
{
multiple ?
<MultipleSelect
selectValue={selectValue}
onSelectChange={onSelectChange}
renderValue={renderValue}
>
{children}
</MultipleSelect>
:
<SingleSelect
selectValue={selectValue}
onSelectChange={onSelectChange}
renderValue={renderValue}
>
{children}
</SingleSelect>
);
};
export default Select;
and I want to be able to use my component similarly to how the native html <select>
is used. Like so:
<Select selectValue={selectValue} onSelectChange={onSelectChange} multiple>
{options}
</Select>
Upvotes: 1
Views: 209
Reputation: 511
You can use in
to check if multiple is set like so:
type MultipleSelectProps = {
multiple: true;
value: string[];
onChange: (newValue: string[]) => void;
};
type SingleSelectProps = {
value: string;
onChange: (newValue: string) => void;
};
type SelectProps = MultipleSelectProps | SingleSelectProps;
function SingleSelect(props: SingleSelectProps) {
return null;
}
function MultiSelect(props: MultipleSelectProps) {
return null;
}
function Select(props: SelectProps) {
return "multiple" in props ? (
<MultiSelect {...props} />
) : (
<SingleSelect {...props} />
);
}
Upvotes: 1
Reputation: 111
This is how I would do it. By defining the kind TypeScript is aware of the type of selectValue and other that might change.
import React, { ReactNode } from 'react';
import MultipleSelect from './MultipleSelect';
import SingleSelect from './SingleSelect';
type SelectProps = {
kind: 'multiple',
selectValue: string[];
onSelectChange: (values: string[]) => void;
renderValue?: (value: string[]) => React.ReactNode;
} | {
kind: 'single',
selectValue: string;
onSelectChange: (values: string) => void;
renderValue?: (value: string) => React.ReactNode;
}
const Selector = (props: SelectProps, children): JSX.Element => {
return (
<>
{
props.kind === 'multiple'
?
<MultipleSelect
selectValue={props.selectValue}
onSelectChange={props.onSelectChange}
renderValue={props.renderValue}
>
{children}
</MultipleSelect>
:
<SingleSelect
selectValue={props.selectValue}
onSelectChange={props.onSelectChange}
renderValue={props.renderValue}
>
{children}
</SingleSelect>
}
</>
)
}
export default Selector
Upvotes: 1