I use React Aria Components' RadioGroup
with controlled values to display an array of options. The options are typed with an enum
. To store the selected option locally, I use a useState
with the first option as a default value.
However I get Argument of type 'string' is not assignable to parameter of type 'SetStateAction<Option>'.
with this code:
import { useState } from "react";
import { Label, Radio, RadioGroup } from "react-aria-components";
enum Option {
ONE = "option_one",
TWO = "option_two",
}
export default function App() {
const options: Option[] = [Option.ONE, Option.TWO];
const [selectedOption, setSelectedOption] = useState(options[0]);
return (
<RadioGroup
onChange={(value) => setSelectedOption(value)}
value={selectedOption}
>
<Label>Options</Label>
{options.map((option) => (
<Radio key={option} value={option}>
{option}
</Radio>
))}
</RadioGroup>
);
}
The useState
id correctly inferred as Option
, but the value
is considered a string
.
The only thing working so far seems to be a type assertion like:
export default function App() {
// ...
return (
<RadioGroup
onChange={(value) => setSelectedOption(value as Option)}
value={selectedOption}
>
// ...
</RadioGroup>
);
}
I generally try to avoid type assertions. Is there another smoother way to get the right type for the onChange
event?
Since you're starting with a string (value
), you have to tell TypeScript you know it's a valid Option
. There are at least two ways to do this:
Use a type predicate or an assertion function.
Use a type assertion.
I prefer using a type predicate or assertion function since it not only satisfies TypeScript, but also checks things at runtime so that if I make a mistake (for instance: adding a hardcoded "none" option to the list that isn't in Option
), I get a clear error I can fix. Here you probably want an assertion function, since there isn't a valid code path where the value wouldn't be correct:
function assertIsOption(value: string): asserts value is Option {
if (!Object.keys(Option).includes(value)) {
throw new Error(`Invalid Option value: "${value}"`);
}
}
(There are various ways to spin that; the point is just that it's an assertion function that checks value
.)
Then use it in your click handler:
<RadioGroup onChange={(value) => {
assertIsOption(value);
setSelectedOption(value);
}} value={selectedOption}>
Playground example (note: it takes a long time for the playground to finish loading the libs).