Reputation: 249
I have this piece of code where I am trying to wire up the Combobox Component from Headless UI with react-hook-form. Whenever I try to enter a different value and select a different option it gives me an error saying -
Cannot read properties of undefined (reading 'name')
I have tried a lot of different variations but I fail to use the Combobox
with register
. Any help would be appreciated. I want to use register
to make this work and don't want to use the other Controller
method of doing ways from react-hook-form
.
import React from "react";
import { useState } from "react";
import { Combobox } from "@headlessui/react";
import { useForm } from "react-hook-form";
const people = [
{ id: 1, name: "Durward Reynolds" },
{ id: 2, name: "Kenton Towne" },
{ id: 3, name: "Therese Wunsch" },
{ id: 4, name: "Benedict Kessler" },
{ id: 5, name: "Katelyn Rohan" },
];
function Example() {
const [query, setQuery] = useState("");
const filteredPeople =
query === ""
? people
: people.filter((person) => {
return person.name.toLowerCase().includes(query.toLowerCase());
});
const {
register,
handleSubmit,
setValue,
formState: { errors },
} = useForm();
const submit = (data) => {
console.log(JSON.stringify(data));
};
return (
<form onSubmit={handleSubmit(submit)}>
<Combobox
as="div"
name="assignee"
defaultValue={people[0]}
{...register("assignee")}
>
<Combobox.Input
onChange={(event) => setQuery(event.target.value)}
displayValue={(person) => person.name}
/>
<Combobox.Options>
{filteredPeople.map((person) => (
<Combobox.Option key={person.id} value={person}>
{person.name}
</Combobox.Option>
))}
</Combobox.Options>
</Combobox>
<button>Submit</button>
</form>
);
}
export default function check() {
return (
<div>
<Example />
</div>
);
}
Upvotes: 8
Views: 4709
Reputation: 1
The way I solved this was through using useController
hook. (I assumed OP is skeptical about using <Controller>
component, not the useController
hook + I was not able to do this using register
either)
First you need control
from useForm
:
const {
control,
register,
handleSubmit,
formState: { errors },
} = useForm<DataSchema>()
Then you need field
from useController
hook:
// name is the name of your component used by react hook form
const { field } = useController({ control, name });
And now the trick is that you pass value
and onChange
from field
to <Combobox>
component and onBlur
, name
and/or ref
to <Combobox.Input>
:
<Combobox value={field.value} onChange={field.onChange} by="id">
<Combobox.Input
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
// you can still handle onChange however you want,
// as this input value is used to filter combobox options
onChange={(e) => setFilter(e.target.value)}
/>
</Combobox>
You can find out what each of field
properties does on react-hook-form
site here under Tips
If your combobox
component is a child of a component where you use useForm
, and you're using typescript, you can define types for control
and name
as props like this:
type Props<TFormValues extends FieldValues> = {
control: Control<TFormValues>;
name: Path<TFormValues>;
// other props
};
export default function MyCombobox<TFormValues extends FieldValues>({
control,
name,
// other props
}: Props<TFormValues>) {
// component
};
And if you need to pass field
down to the child component:
type Props<TFormValues extends FieldValues> = {
field: ControllerRenderProps<TFormValues, Path<TFormValues>>;
// other props
};
export default function MyInput<TFormValues extends FieldValues>({
field,
// other props
}: Props<TFormValues>) {
//component
};
Upvotes: 0
Reputation: 431
Unfortunately it is not possible to use Combobox
without Controller
:
The problem is that Combobox
calls onChange
with bare value
but RHF's onChange
handler returned by register
expects an event
instead.
Upvotes: 0
Reputation: 1844
It's likely not a good idea to attach react-hook-form
handlers to the Combobox
directly.
Input > onChange
will give you an event with a string target.value
, not a Location / User / ...
model from the API. Will you do a copy request to "unpack" it in handleSubmit
? And, if so, – how are you going to handle an API error there?!The input might be associated with an API error at the Combobox
level. You'll have to be extra careful to distinguish "successful" and "failed" strings at the form component level.
The string might be non-parsable at the form component level. Especially with "multiple" mode, where you can render an aggregate info, like "3 items are selected" in the input. And THAT will be your value if you expand register
to the Combobox.Input
.
Finally, in some other (non HeadlessUI) Combobox
implementations, the value will preserve raw user input.
For example:
Combobox
takes a new value but the Combobox.Input
value still holds "United"You probably want to stick to a portable and future-proof approach.
The conclusion is the same: let Combobox
parse and translate values for you. Provide onChange
to Combobox
, not Combobox.Input
. But that is possible only with controlled RHF API variant.
Upvotes: 0
Reputation: 1
const { register } = useFormContext();
<Combobox.Input
{...register(id)}
id={id}
onChange={(event) => setQuery(event.target.value)}
/>
Upvotes: -2