How to control select mutltiple (v1)? #389
-
I want to control a hidden select. The following implementation works but isn't allowed by the library's types.
(I would like to deal with most of the edge-cases internally in the field to avoid preprocessing & transforms in every formSchema) const formSchema = z.object({
multi: z
.array(z.string())
.optional()
multi2: z.array(z.string()).nonempty(),
})
export default function Component() {
const [form, fields] = useForm({
defaultValue: {
multi: options[0].value, // Allowed to be a string
multi2: [options[1].value], // Not allowed to be a string
},
constraint: getZodConstraint(formSchema),
/*...*/
})
return /*...*/
}
export interface MultiSelectFieldProps
extends Pick<MultiSelectProps, 'options'> {
name: FieldName<string[]>
label: string
placeholder?: string
description?: string
initialOptions?: ComboboxProps['options']
disabled?: boolean
className?: string
}
export function MultiSelectField({
name,
label,
options,
initialOptions,
placeholder,
description,
disabled,
className,
}: MultiSelectFieldProps) {
const [meta] = useField(name)
const control = useInputControl(meta) // not allowed to be (string | undefined)[]
const values =
Array.isArray(control.value) ?
control.value
: control.value?.split(',').filter(Boolean)
const actualOptions = options || initialOptions || []
const selected = actualOptions?.filter(
option => values?.includes(option.value),
)
const shadowDomSelectorProps = getSelectProps(meta)
delete shadowDomSelectorProps.defaultValue
return (
<div className={className}>
<Label htmlFor={meta.id}>
{label}
{meta.constraint?.required && <span aria-hidden>*</span>}
</Label>
<select
value={values}
onChange={() => {}}
{...shadowDomSelectorProps}
hidden
>
{actualOptions.map(option => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
<MultiSelect
value={selected}
onValueChange={selectedOptions => {
control.change(selectedOptions.map(opt => opt.value)) // string[] not assignable
}}
onOpenChange={open => {
if (!open) control.blur()
}}
placeholder={placeholder}
options={actualOptions}
disabled={disabled}
className="mt-1 flex w-full font-normal text-foreground hover:bg-background"
/>
{description && (
<p className="pt-1 text-sm text-muted-foreground">{description}</p>
)}
<div className="min-h-[32px] pb-2 pt-1">
{!meta.valid ?
<ErrorList id={meta.errorId} errors={meta.errors} />
: null}
</div>
</div>
)
} |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments 7 replies
-
I think it is fair to expect I am not sure if I can get this updated before the v1 release but I will prioritize this whenever possible. Feel free to take a stab on it and create a PR. :) |
Beta Was this translation helpful? Give feedback.
-
Thanks for the quick answer. I'm happy contribute with the update. I'll give it a go as soon as I'm done migrating to v1 :) |
Beta Was this translation helpful? Give feedback.
-
@edmundhung Is there any update around this? I have a combobox component I'm trying to use with conform, it expects and emits onchange with an array. For now I'm just using a comma delimited string in the value but I'd prefer not to. |
Beta Was this translation helpful? Give feedback.
I think it is fair to expect
useInputControl
to work with multiple select here. What you are doing in the example make sense to me. I guess we just need to tweak the implementation so that it works with multi<select />
properly when it receives an array withcontrol.change
.I am not sure if I can get this updated before the v1 release but I will prioritize this whenever possible. Feel free to take a stab on it and create a PR. :)