Reputation: 489
I have this Select component:
type SelectProps = {
title: string;
name: string;
items: string[];
onChange: (e: ChangeEvent<HTMLSelectElement>) => void;
defValue: string;
};
const Select: FC<SelectProps> = ({ title, name, items, onChange, defValue }) => {
return (
<>
<label htmlFor={name}>
{title}:
</label>
<select name={name} onChange={onChange} defaultValue={defValue}>
{items.map((item) => (
<option value={item} key={item}>
{item}
</option>
))}
</select>
</>
);
};
and I'm handling onChange with this function:
const onThemeChange = (e: ChangeEvent<HTMLSelectElement>) => {
const theme = e.target.value;
setTheme(theme)
};
...
<Select
title='Theme'
defValue={props.theme}
name='theme'
items={['light', 'dark']}
onChange={onThemeChange}
/>
My setTheme
action creator accepts argument with type 'light' | 'dark'
, so I'm getting an error:
Argument of type 'string' is not assignable to parameter of type '"light" | "dark"'
What is the best way to solve this issue?
Upvotes: 0
Views: 4456
Reputation: 25850
There is away, but it requires a little trick.
First, let's recognize the relationships between types in your SelectProps
:
items
are string literalsonChange
, the event will have a target.value
equal to one of your items
defValue
should also be one of the items
To express these constraints, we need to use a generic interface.
type SelectProps<T extends string> = {
title: string;
name: string;
items: T[];
onChange: (e: ChangeEvent<HTMLSelectElement> & { target: { value: T }}) => void;
defValue: DeferTypeInference<T>;
};
const Select = function<T extends string>({ title, name, items, onChange, defValue }: SelectProps<T>) {
return (
<>
<label htmlFor={name}>
{title}:
</label>
<select name={name} onChange={onChange} defaultValue={defValue}>
{items.map((item) => (
<option value={item} key={item}>
{item}
</option>
))}
</select>
</>
);
};
We have achieved everything.
<Select
title='Theme'
defValue="light" // only "light" or "dark" are accepted
name='theme'
items={['light', 'dark']}
onChange={event => event.target.value} // event.target.value is "light" or "dark"
/>
Note the use of a type called DeferTypeInference<T>
. If you're curious why it's there, check out this answer.
Upvotes: 2
Reputation: 42596
The quickest way of doing so would be to do type assertions.
Assuming that this is how you initialised the state,
type ThemeState = 'light' | 'dark';
const [theme, useTheme] = useState<ThemeState>('light');
And then, on your onThemeChange
method, you will assert the value
as ThemeState
const theme = e.target.value as ThemeState;
Upvotes: 0