I'm struggling to get a Headless UI Listbox
implementation working successfully with Formik
. After reading all the docs and scouring StackOverflow, I'm yet to find an answer.
I can get the Listbox
to function perfectly well on its own, and Formik
is working with other, less complex (but still custom) components. That said, I can't get them working in unison. Whenever I change the selection in the Select
component, I can successfully update the Headless UI
state (in as much as it updates to the correct value) and the Formik
state, but for some reason the active
and selected
properties (within Headless UI
) aren't working correctly (always false
).
I assume what I need to do is to make use of the onChange
handler so that it updates both the Headless UI
state (to keep the active
and selected
properties updated) and the Formik
value, but this doesn't seem to work as expected.
Does anyone have any suggestions? Please see a minimal representation of the code below:
export function TestForm() {
return (
<Formik
initialValues={{ testing: {} }}
onSubmit={ alert("Submitted"); }
>
<Form>
<Select
name="testing"
items={[
{ id: 1, name: "Test 1" },
{ id: 2, name: "Test 2" },
{ id: 3, name: "Test 3" },
]}
/>
</Form>
)
}
export function Select(props) {
// Get field properties
const [field, meta, helpers] = useField(props);
// Set initial value for `Select`
const [selectedItem, setSelectedItem] = useState(null);
// On change, update `Headless UI` and `Formik` values
const handleChange = (newValue) => {
setSelectedItem(newValue); // Update the Headless UI state
helpers.setValue(newValue); // This seems to "break" the Headless UI state
};
// Return `Select` structure
return (
<Listbox
value={selectedItem}
name={props.name}
onChange={handleChange}
// onBlur={field.onBlur} ...Commented out for now as trying to figure out issue with `onChange`
>
<Listbox.Label>Test Label:</Listbox.Label>
<Listbox.Button>
{selectedItem ? selectedItem.name : "-- Select --"}
</Listbox.Button>
<Listbox.Options>
{props.items.map((item) => {
return (
<Listbox.Option
className={({ active }) => (active ? "active" : "")}
key={item.id}
value={item}
>
{({ selected }) => (
<>
{item.name}
{selected ? <CheckIcon /> : null}
</>
)}
</Listbox.Option>
);
})}
</Listbox.Options>
</Listbox>
);
}
I managed to make it work like this:
import { useField } from "formik";
interface SelectProps {
name: string;
label: string;
options: string[];
}
export const Select: React.FC<SelectProps> = ({
name,
label,
options,
}) => {
const [field] = useField({ name });
return (
<Listbox
value={field.value}
onChange={(value: string) => {
field.onChange({ target: { value, name } });
}}
>
...
</Listbox>
);
};