Forms
Conform Async Select
Async Select wired to Conform: derives name and validity from field metadata, mirrors the remote-loaded selection into a hidden input for submission, and renders inline errors.
conformselectsingle-selectasyncformvalidationsearch
Bound to a Conform form
Submit without choosing anyone to see the validation error wired from field metadata. Options load from the source as you search.
Source
Copy this into your project. Resolve its dependencies from the registryDependencies in the component's API entry.
"use client"
import type { FieldMetadata } from "@conform-to/react"
import { useMemo } from "react"
import {
AsyncSelect,
type AsyncSelectOption,
type AsyncSelectProps,
} from "@/components/async-select"
import { FieldError, Label } from "@/components/field"
interface ConformAsyncSelectProps<T extends AsyncSelectOption>
extends Omit<AsyncSelectProps<T>, "name" | "defaultValue" | "isInvalid"> {
// A single-value select bound to a string form value. Only
// name/id/initialValue/errors are read off the metadata.
field: FieldMetadata<string>
label?: string
/**
* Initial selection as a full option object so the input can show its label.
* Falls back to the field's initial id (label = id) when omitted — pass this
* whenever the label differs from the stored id.
*/
defaultSelected?: T
}
/**
* ConformAsyncSelect — Async Select wired to Conform.
*
* Binds a single-value Conform field to the quebi Async Select: derives name
* and validity from the field metadata, mirrors the remote-loaded selection
* into a hidden input for submission, and renders inline errors. Provide `load`
* to fetch options and `defaultSelected` to seed the input label from the
* field's initial value.
*/
export function ConformAsyncSelect<T extends AsyncSelectOption>({
field,
label,
defaultSelected,
...props
}: ConformAsyncSelectProps<T>) {
const defaultValue = useMemo<T | null>(() => {
if (defaultSelected) return defaultSelected
const initial = field.initialValue as string | undefined
return initial ? ({ id: initial, name: initial } as T) : null
}, [defaultSelected, field.initialValue])
return (
<div className="flex flex-col gap-1.5">
{label && <Label htmlFor={field.id}>{label}</Label>}
<AsyncSelect<T>
{...props}
id={field.id}
name={field.name}
defaultValue={defaultValue}
isInvalid={!!field.errors}
/>
<FieldError>{field.errors?.join(", ")}</FieldError>
</div>
)
}