Skip to content

Commit 8e72b4e

Browse files
committed
Handle empty string option value
1 parent cef7f91 commit 8e72b4e

2 files changed

Lines changed: 32 additions & 8 deletions

File tree

src/components/select/index.tsx

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ export type SelectProps<T = string> = {
1818
value?: T
1919
}
2020

21+
// Avoid throwing an error if <Select.Item /> has an empty string value.
22+
// https://github.com/radix-ui/primitives/blob/main/packages/react/select/src/select.tsx#L1277
23+
const EMPTY_VALUE_PLACEHOLDER = '__empty__'
24+
2125
export const Select = <T extends string = string>({
2226
className,
2327
defaultValue,
@@ -28,24 +32,43 @@ export const Select = <T extends string = string>({
2832
placeholder = 'Select an option',
2933
value,
3034
}: SelectProps<T>) => {
35+
const normalizeValue = (val: T | undefined) => {
36+
if (val === '') return EMPTY_VALUE_PLACEHOLDER
37+
return val
38+
}
39+
40+
const denormalizeValue = (val: string): T => {
41+
if (val === EMPTY_VALUE_PLACEHOLDER) return '' as T
42+
return val as T
43+
}
44+
45+
const handleValueChange = (newValue: string) => {
46+
if (handleChange) {
47+
handleChange(denormalizeValue(newValue))
48+
}
49+
}
50+
3151
return (
3252
<div className={cn('space-y-2', className)}>
3353
{label && <Label>{label}</Label>}
3454
<BaseSelect
35-
value={value as string}
36-
onValueChange={handleChange as ((value: string) => void) | undefined}
37-
defaultValue={defaultValue as string}
55+
value={normalizeValue(value)}
56+
onValueChange={handleValueChange}
57+
defaultValue={normalizeValue(defaultValue)}
3858
disabled={disabled}
3959
>
4060
<SelectTrigger className="text-foreground font-medium data-[size=default]:h-10 w-full bg-background focus-visible:ring-ring/20">
4161
<SelectValue placeholder={placeholder} />
4262
</SelectTrigger>
4363
<SelectContent>
44-
{options.map(option => (
45-
<SelectItem key={String(option.value)} value={String(option.value)}>
46-
{option.label}
47-
</SelectItem>
48-
))}
64+
{options.map(option => {
65+
const itemValue = option.value === '' ? EMPTY_VALUE_PLACEHOLDER : String(option.value)
66+
return (
67+
<SelectItem key={itemValue} value={itemValue}>
68+
{option.label}
69+
</SelectItem>
70+
)
71+
})}
4972
</SelectContent>
5073
</BaseSelect>
5174
</div>

src/stories/Select/Select.stories.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const options = [
1818
{ value: 'rofl_register', label: 'ROFL Register' },
1919
{ value: 'rofl_remove', label: 'ROFL Remove' },
2020
{ value: 'rofl_update', label: 'ROFL Update' },
21+
{ value: '', label: 'Unknown' },
2122
]
2223

2324
export const Default: Story = {

0 commit comments

Comments
 (0)