ui
ui copied to clipboard
Cannot use value other than string on Select's onValueChange
Form schema and hook?
const FormSchema = z.object({
theme: z.enum(['light', 'dark', 'system']),
});
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
defaultValues: {
theme: theme as z.infer<typeof FormSchema>['theme'],
},
});
Form:
<FormField
control={form.control}
name='theme'
render={({ field }) => (
<FormItem>
<FormLabel>Thema</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
Error:
Type '(event: "light" | "dark" | "system" | ChangeEvent<Element>) => void' is not assignable to type '(value: string) => void'.
Types of parameters 'event' and 'value' are incompatible.
Type 'string' is not assignable to type '"light" | "dark" | "system" | ChangeEvent<Element>'.ts(2322)
The onValueChange is typed as such:
export interface SelectProps {
...
onValueChange?(value: string): void;
How can I make this work with enum?
@niebag Apparently it's a problem related to react-hook-form
v7.45.0, which implemented a stricter type for the onChange
callback. Version 7.45.1
reverted this feature so it should work normally now.
Let me know.
@niebag Apparently it's a problem related to
react-hook-form
v7.45.0, which implemented a stricter type for theonChange
callback. Version7.45.1
reverted this feature so it should work normally now.Let me know.
Hi Daniele, thanks for responding.
I am already running ^7.45.1
. My code snippets in the initial description are running ^7.45.1
ssame issue here any fixes 🤔
define enum
const ThemeEnum = z.enum(["light", "dark", "system"])
type ThemeEnum = z.infer<typeof ThemeEnum>
schema
const formSchema = z.object({
theme: ThemeEnum.optional(),
})
form field
<FormField
control={form.control}
name="theme"
render={({ field }) => (
<FormItem>
<FormLabel>Theme</FormLabel>
<Select
onValueChange={(value) => field.onChange(ThemeEnum.parse(value))}
defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a theme" />
</SelectTrigger>
</FormControl>
<SelectContent>
{ThemeEnum.options.map((theme) => (
<SelectItem key={theme} value={theme}>
{theme}
</SelectItem>
))}
</SelectContent>
</Select>
</FormItem>
)}
/>
Same issue here... So inconvenient...
"react-hook-form": "^7.45.4",
I think I found a solution for this, in my case I wanted to handle an object as a value, I stringified everything and then I parsed them.
example:
interface SelectFieldProps {
items: SelectListItem[];
field: {
onChange: (value: string) => void;
onBlur: () => void;
value: object | undefined; // * define the value type here
name: string;
ref: Ref<any>;
};
}
const SelectField = ({
items,
field,
}: SelectFieldProps) => {
const handleFieldValue = (value: string) => {
const parsedObject = JSON.parse(value); // *parse the object here
console.log(parsedObject);
};
return (
<Select onValueChange={handleFieldValue} disabled={disabled}>
<SelectTrigger>
<SelectValue placeholder={placeholder} />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>{label}</SelectLabel>
{items.map((item) => (
<SelectItem
key={item.id}
value={JSON.stringify(item)} // *stringify the object here
textValue={item.name}
defaultValue="Select"
>
{disabled ? disabledName : item.name}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
);
};
export default SelectField;
Bump. Silly that this doesn't work.
ANNYONE found a solution, I cannot use other types than string
Same issue when using the Select component
Same issue. So frustrated.
Very annoying typing here.
Components Select
, MenuItem
from @mui/material
don't have such problem. I can easily use number
type as value, no typescript compiler issues.
It would be really great for shadcn be flexible too
Any update about this ?
Probably not an ideal solution, but I solved this by creating a component that wraps Select
and combines it with a label and optional errors. This wrapper has a type guard that accepts a wider type (from conform
) in my case, and then checks if it's a string. This does not solve the problem if you actually want to use a different type than string in runtime, but it solved the typing issue for me:
import { Label } from './label.tsx'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
type SelectProps,
} from './select.tsx'
interface SelectFieldProps {
options: Array<{ value: string; label: string }>
labelProps: JSX.IntrinsicElements['label']
selectProps: Omit<SelectProps, 'defaultValue'> & {
id?: string
defaultValue?: ConformSelectProps['defaultValue']
disabled?: boolean
placeholder?: string
'aria-invalid'?: boolean
'aria-describedby'?: string
}
errors?: ListOfErrors
className?: string
}
// We need to check if the defaultValue is of string or undefined,
// because conform-to/react it's `getSelectProps` returns a wider type,
// which is not accepted by Radix SelectPrimitive
function isValidSelectValue(value: unknown): value is string | undefined {
return typeof value === 'string' || value === undefined
}
export function SelectField({
options,
labelProps,
selectProps,
errors,
className,
}: SelectFieldProps) {
const { placeholder, defaultValue, ...buttonProps } = selectProps
const fallbackId = React.useId()
const id = buttonProps.id ?? fallbackId
const errorId = errors?.length ? `${id}-error` : undefined
if (!isValidSelectValue(defaultValue)) {
throw new Error(
'Please only use string or undefined as defaultValue for SelectField.',
)
}
return (
<div className={cn('flex flex-col gap-y-2', className)}>
<Label {...labelProps} htmlFor={id} />
<Select {...buttonProps} defaultValue={defaultValue}>
<SelectTrigger
aria-invalid={selectProps['aria-invalid']}
aria-describedby={selectProps['aria-describedby']}
>
<SelectValue placeholder={placeholder} />
</SelectTrigger>
<SelectContent id={id}>
{options.map(option => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
{errorId ? <ErrorList id={errorId} errors={errors} /> : null}
</div>
)
}