I made a customized Select
component with useState
and onChange
. And I tried to use it with react-hook-form.
Here's the code for Select
component.
const Select = forwardRef<HTMLInputElement, ComponentPropsWithoutRef<"input">>((props, ref) => {
const internalRef = useRef<HTMLInputElement>(null);
const [fruit, setFruit] = useState("");
const [isOpen, setIsOpen] = useState(false);
useImperativeHandle<HTMLInputElement | null, HTMLInputElement | null>(
ref,
() => internalRef.current,
[]
);
const onClickFruit = (e: React.MouseEvent<HTMLLIElement>) => {
const fruit = e.currentTarget.textContent as string;
setFruit(fruit);
setIsOpen(false);
};
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setFruit(e.target.value);
};
return (
<div>
<div onClick={() => setIsOpen((prev) => !prev)}>
<input
id="input"
type="text"
ref={internalValue}
value={fruit}
// problem No.1 comes from below.
onChange={onChange}
{...props}
/>
</div>
{isOpen && (
<ul>
<li onClick={onClickFruit}>apple</Li>
<li onClick={onClickFruit}>peach</Li>
<li onClick={onClickFruit}>grape</Li>
</ul>
)}
</div>
);
};
And I used this component in the Form
component with react-hook-form as below.
interface FormValue {
fruit: string;
}
const Form = () => {
const {register, handleSubmit, formState: {errors}} = useForm<FormValue>({ mode: "onChange" });
const onSubmit = (e: FieldValues) => {
console.log(e);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Select {...register("fruit", {
validate: (value) => value !== "peach" || "No Peach Allowed!",
})}
/>
<button type="submit">submit</button>
</form>
);
};
Problems are,
{...props}
comes before onChange
. It seems react-hook-form's onChange overwrites input's onChange. What if I send onChange
function as prop and want to let both onChange
functions work?onChange
mode.How can I solve them? Please help.
p.s. I tried Controller
. I could write but it logged {fruit: undefined} when the form was submitted. Even worse, the input's value wasn't updated when I click the fruits in li
.
Issue 1: To fix this, destructure props
to {onChange, ...restProps}
and to input
component pass only restProps
so that RHF's onChange
doesn't override custom onChange
Issue 2 & 3 : You need to call destructured onChange
in both onChange
and onClickFruit
functions so that RHF will have updated values. So, these functions can be updated as below
const onClickFruit = (e: React.MouseEvent<HTMLLIElement>) => {
const fruit = e.currentTarget.textContent as string;
onChange(fruit)
setFruit(fruit);
setIsOpen(false);
};
const onInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setFruit(e.target.value);
onChange(e.target.value);
};
And update input as below
<input
id="input"
type="text"
ref={internalValue}
value={fruit}
onChange={onInputChange}
{...restProps}
/>